Android Open Source - BestBoard Scribe






From Project

Back to project page BestBoard.

License

The source code is released under:

MIT License

If you think the Android project BestBoard listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package digitalgarden.magicmerlin.scribe;
/* www  .  j  av a 2  s . c  om*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.lang.Thread.UncaughtExceptionHandler;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

/**
 * Alternatv log.
 * <p>
 * Az osztly statikus metdusokat tartalmaz, melyek a log zenetek teljes kirst nmagukban elvgzik.
 * Az {@code AndroidManifest.xml}-ben engedlyek megadsa szksges:
 * {@code <uses-permission 
 *  android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> }
 * Az {@code android.permission.READ_LOGS} megadsa tbb nem szksges.
 * <p>
 * Hrom irnyban kpes log zenetet kldeni:
 * <ul>
 * <li>system log</li>  
 * <li>file log</li>  
 * <li>toast log (alpertelmezetten kikapcsolva)</li>
 * </ul>
 * A log teljes egszben ill. az egyes irnyok egyenknt ki- ill. bekapcsolhatak.
 * A system log alatti tag bellthat.
 * A file log esetben a directory path, ill. a file neve megadhat, ill. a teljes file trlhet? is.
 * Amennyiben a file log hossza a belltott rtket meghaladja, a file log tbb lpcs?ben archivlhat.
 * <p>
 * Ha a program egy rszben specilis belltsokat szeretnnk, 
 * akkor a fenti belltsok egyben elmenthet?ek s visszatlthet?ek.
 * <p>
 * A  debug zenetek "limitlhatak": minden zenethez hozzrendelhetnk egy szint (level) rtket. 
 * Ezek az zenetek csak akkor kerlnek elkldsre, ha a szint (level) rtkk a belltott szls?rtkek kz esik.
 * (A NO_LIMIT rtk feletti zenetekre ez a megszorts nem vonatkozik.)
 * rdemes egy adott osztlyban egyforma, de alacsony szint? osztlyoknl alacsony, 
 * mg fels?bb m?kdst vgz? osztlyoknl magasabb rtket belltani. Ezzel elrhet?, hogy a fels? szint? 
 * vezrlst az als szint korltozsval ellen?rizzk (vagy fordtva). 
 * Korltozhatjuk az ellen?rzst akr egyetlen osztlyra is.  
 * <p>
 * Az zenetek tpusai:
 * <ul>
 * <li>TITLE - cm (log file hossz ellen?rzs is!)</li>  
 * <li>NOTE - norml zenet</li>
 * <li>ERROR - hibazenet</li>
 * <li>DEBUG - norml zenet - csak debug log esetn!</li>
 * <li>LOCUS - full.class.method (thread) formban a pozci</li>  
 * </ul>
 * A DEBUG (s vele egytt a LOCUS) tpus log zenet kln engedlyezhet?/letilthat.
 * <p>
 * A log zenetek konfigurlsa:
 * Kt kln konfigurci llthat be, a pros s pratlan szintek (level) konfigurcija eltrhet.
 * A msodlagos (pratlan szint) konfigurci csak akkor kerl bekapcsolsra, ha annak valamelyik rtkt megvltoztattuk,
 * addig a kt konfigurci azonos. A msodlagos konfigurcival m?kd? parancsok a 'Secondary' kiegsztst kaptak, 
 * ill. a pratlan szint? zenetek hasznljk.
 * Msodlagos konfigurcit hasznlhatjuk pl. a felhasznlt/fejleszt?t kln clz log belltsra, 
 * vagy ha egy folyamatot a f? belltsoknl rszletesebben akarunk megfigyelni.
 * <p>
 * Log zenetet brmely szlrl kldhetnk. 
 * A konfigurcik belltsa is 'thread-safe', azonban ezek a belltsok azonnal tkrz?dnek a tbbi szlon, 
 * ezrt clszer? csak main-szlon (mg a tbbi thread indtsa el?tt) elvgezni a belltsokat. 
 * <p>
 * A belltsok statikus vltozkban (azaz application szinten) kerlnek trolsra. Figyelni kell arra, 
 * hogy rtkket az jraindtsok kztt meg?rizhetik. rdemes ezrt a program belpsi pontjain 
 * a konfigurct(kat) inicializlni:
 * {@link #init(Context)}
 * Ilyenkor az alaprtelmezett log file neve a package nvvel megegyezik; 
 * vagyis a klnbz? programjaink elklnlten jegyzik fel a log zeneteket. 
 * <p>
 * A 'system-log' kezelse is lehetsges: 
 * A system log zeneteket tmsolhatjuk a file-logra (els?dleges konfigurci), vagy trlhetjk.
 * API 16 felett a system log-bl csak a sajt programunk log zenetei olvashatak ki.
 * <p>
 * Lehetsges az el nem kapott kivtelek file-logra kldse is (primr konfigurci). 
 * A log file-ban a kivtelek fordtott sorrendben szerepelnek: 
 * azaz a kivtel-kaszkdot indt kivtel ll az els? helyen. 
 * A stack-trace teljes egszben kirsra kerl, nincs rvidts.
 * A kirst kvet?en a vezrlst a rendszer visszakapja, 
 * gy a system-log-ra az android rendszer rja ki a hibazeneteket, 
 * valamint a programot is le fogja lltani. 
 */
public class Scribe
  {
  /************************************************************
   *                                                          *
   *  Default constant values                                 *
   *                                                          *
   ************************************************************/

  /** Log file's default extension */
  public static final String DEFAULT_FILE_EXT = ".log";  
  /** Log file's default name */
  public static final String DEFAULT_FILE_NAME = "scribe" + DEFAULT_FILE_EXT;  
  /** Log file's default directory */
  public static final String DEFAULT_DIRECTORY = "";
  /** Default tag for system log */
  public static final String DEFAULT_LOG_TAG = "SCRIBE";

  /** Log file will be archived above this size */
  public static final long MAX_LOGFILE_LENGTH = 1024*1024;
  /** Maximum number of archived log files */
  public static final int MAX_LOGFILE = 8;
  
  /** Debug cannot be limited above this value */
  public static final int NO_LIMIT = 1000;
  /** Not limited level value for secondary config */
  public static final int NO_LIMIT_SECONDARY = NO_LIMIT + 1;
    
  // Constants returned by methods
  /** Returned value: everything was OK */ 
  public static final String OK = "Log ready";
  /** Returned value: logging is disabled */ 
  public static final String OFF = "Log disabled";
  /** Logging error: returned value starts with this */ 
  public static final String LOGFILE_ERROR = "LOGFILE ERROR: ";
  
  /** Path separator */
  private static final String SEPARATOR = System.getProperty("line.separator");
  
  /** Primary config */
  private static final int PRIMARY_CONFIG = 0;
  /** Secondary config */
  private static final int SECONDARY_CONFIG = 1;

  
  /************************************************************
   *                                                          *
   * Private config variables                                 *
   *                                                          *
   ************************************************************/

  /** Config settings, organised in one nested class */
  private static class Config
    {
    /** Is logging enabled? (Main switch) */ 
    private boolean enabled;
    /** Is debug-logging enabled? */
    private boolean debugEnabled;
    /** System-log tag. Null disables */  
    private String logTag;
    /** File-log file name. Null disables */ 
    private String fileName;
    /** File-log path (on sd-card). Cannot be null */
    private String directoryName;
    /** Toast-log context (of Activity). Null disables. */ 
    private Context context;
    /** Message levels below this limit are disabled */
    private int minLimit;
    /** Message levels above this limit are disabled. No restriction above NO_LIMIT! */
    private int maxLimit;
    /** Is time stamp enabled for file-log messages? */
    private boolean timeStampEnabled;
    /** Is space stamp (class.method) enabled for file-log messages? */
    private boolean spaceStampEnabled;
    }

  /** Default log file name: could be changed to package.extension */
  private static volatile String defaultFileName = DEFAULT_FILE_NAME;
  
  /** Temporary variable to set up config[] first */
  private static final Config primaryConfig = new Config();
  
  /** 
   * Settings for the two configurations.
   * Reference of the array is final; second element can be changed to secondaryConfig. 
   * Before its activation secondary config points to the same config as primary config.
   */
  private static final Config config[] = { primaryConfig, primaryConfig };
  
  /** Separate locks for accessing the two configurations */
  private static final Object[] lock = { new Object(), new Object() };
  // Both config[0] and [1] point to primaryConfig. But only config[0] could be changed
  // (locked by lock[0]). Writing config[1] means activation of secondary config, so it
  // will not point to primaryConfig any more. config[1] is locked by lock[1]. 
  
  /** Fill up default values at first start */ 
  static 
    {
    resetAll(PRIMARY_CONFIG);
    }

  
  /************************************************************
   *                                                          *
   * Synchronized private methods for configurations          *
   *                                                          *
   * 'config' array is final, could not change                *
   * 'config[0]' will always point to primary config          *
   * 'config[1]' will point to primary config, then           *
   *            to secondary config (after activation)        *
   * 'lock[0]' will lock config[0] and its variables          *
   * 'lock[1]' will lock config[1] and its variables          *
   *                                                          *
   * Config class cannot synchronize itself, because          *
   * config[1] could change, and should be synchronized       *
   * as well!!                                                *
   *                                                          *
   ************************************************************/

  /**
   * Activate secondary config
   * @param conf PRIMARY/SECONDARY configuration
   */
  private static void activateSecondaryConfig( int conf )
    {
    if ( conf != SECONDARY_CONFIG )
      return;
    
    synchronized ( lock[SECONDARY_CONFIG] )
      {
      if ( config[PRIMARY_CONFIG] != config[SECONDARY_CONFIG] )
        return;
      }

    // clone is needed only here
    Config secondaryConfig = new Config();
    synchronized ( lock[PRIMARY_CONFIG] )
      {
      secondaryConfig.enabled = config[PRIMARY_CONFIG].enabled;
      secondaryConfig.debugEnabled = config[PRIMARY_CONFIG].debugEnabled;
      secondaryConfig.logTag = config[PRIMARY_CONFIG].logTag;
      secondaryConfig.fileName = config[PRIMARY_CONFIG].fileName;
      secondaryConfig.directoryName = config[PRIMARY_CONFIG].directoryName;
      secondaryConfig.minLimit = config[PRIMARY_CONFIG].minLimit;
      secondaryConfig.maxLimit = config[PRIMARY_CONFIG].maxLimit;
      secondaryConfig.timeStampEnabled = config[PRIMARY_CONFIG].timeStampEnabled;
      secondaryConfig.spaceStampEnabled = config[PRIMARY_CONFIG].spaceStampEnabled;
      secondaryConfig.context = null;
      }
    
    synchronized ( lock[SECONDARY_CONFIG] )
      {
      // Just to be sure, that it is not changed during this method. 
      if ( config[PRIMARY_CONFIG] == config[SECONDARY_CONFIG] )
        config[SECONDARY_CONFIG] = secondaryConfig;
      // If already changed, we do not modify the previous "secondary config"
      }
    }
  
  /**
   * Resets all log config settings to original values. 
   * @param conf PRIMARY/SECONDARY configuration
   */
  private static void resetAll( int conf )
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] )
      {
      config[conf].enabled = true;
      config[conf].debugEnabled = true;
      config[conf].logTag = DEFAULT_LOG_TAG;
      config[conf].fileName = defaultFileName;
      config[conf].directoryName = DEFAULT_DIRECTORY;
      config[conf].minLimit = 0;
      config[conf].maxLimit = NO_LIMIT;
      config[conf].timeStampEnabled = true;
      config[conf].spaceStampEnabled = false;
      config[conf].context = null;
      }
    }
  
  /**
   * Sets enabled state (main switch)
   * @param conf PRIMARY/SECONDARY configuration
   * @param enable true if enabled
   */
  private static void setEnabled( int conf, boolean enable )
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].enabled = enable;
      }
    }
  
  /**
   * Returns enabled state (main switch) 
   * @param conf PRIMARY/SECONDARY configuration
   * @return true if enabled
   */
  private static boolean isEnabled( int conf )
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].enabled;
      }
    }
  
  /**
   * Sets enabled state of debug-log
   * @param conf PRIMARY/SECONDARY configuration
   * @param enable true if enabled
   */
  private static void setDebugEnabled( int conf, boolean enable )
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].debugEnabled = enable;
      }
    }

  /**
   * Returns enabled state of debug-log 
   * @param conf PRIMARY/SECONDARY configuration
   * @return true if enabled
   */
  private static boolean isDebugEnabled( int conf )
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].debugEnabled;
      }
    }
  
  /**
   * Sets system-log tag 
   * @param conf PRIMARY/SECONDARY configuration
   * @param logTag  tag appearing in system log, null if disabled
   */
  private static void setSysLog( int conf, String logTag ) 
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].logTag = logTag;
      }
    }

  /**
   * Gets system-log tag 
   * @param conf PRIMARY/SECONDARY configuration
   * @return system-log tag, null if disabled
   */
  private static String getSysLog( int conf ) 
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].logTag;
      }
    }

  /**
   * Creates default file name from package name. It will be used as default.
   * @param context application's context
   */
  private static void setDefaultFileName( Context context )
    {
    defaultFileName = context.getPackageName() + DEFAULT_FILE_EXT;
    }

  /**
   * Sets file name for file-log 
   * @param conf PRIMARY/SECONDARY configuration
   * @param fileName  name of log file, null if disabled
   */
  private static void setFileName( int conf, String fileName ) 
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].fileName = fileName;
      }
    }

  /**
   * Sets directory path (on sd-card) for file-log 
   * @param conf PRIMARY/SECONDARY configuration
   * @param directoryName directory path; null and "" mean root on sdcard
   */
  private static void setDirectoryName( int conf, String directoryName ) 
    {
    activateSecondaryConfig( conf );
    if ( directoryName == null )
      directoryName = "";
    synchronized ( lock[conf] ) 
      {
      config[conf].directoryName = directoryName;
      }
    }

  /**
   * Gets log-directory (directory name completed with external storage path).  
   * If path does not exist or is not a directory, root of external storage path will be used. 
   * @param conf PRIMARY/SECONDARY configuration
   * @return log-directory
   */
  private static File getLogDirectory( int conf )
    {
    File directory;
    
    synchronized ( lock[conf] ) 
      {
      directory = new File( Environment.getExternalStorageDirectory(), config[conf].directoryName );
      }
    
    if ( !directory.isDirectory() )
      directory = Environment.getExternalStorageDirectory();

    return directory;
    }
  
  /**
   * Gets log-file (file name completed with directory and external storage path).  
   * If path does not exist or is not a directory, root of external storage path will be used. 
   * @param conf PRIMARY/SECONDARY configuration
   * @return log-file
   */
  private static File getLogFile( int conf )
    {
    synchronized ( lock[conf] ) 
      {
      if (config[conf].fileName == null)
        return null;
      else
        return new File( getLogDirectory(conf), config[conf].fileName);
      }
    }
  
  /**
   * Gets log-file with version (file name completed with directory and external storage path AND version).  
   * If path does not exist or is not a directory, root of external storage path will be used. 
   * @param conf PRIMARY/SECONDARY configuration
   * @param version will be append at the end of the basename (0 omitted)
   * @return log-file
   */
  private static File getLogFile( int conf, int version )
    {
    synchronized ( lock[conf] ) 
      {
      if (config[conf].fileName == null)
        return null;
      else
        {
        String base = config[conf].fileName;
        String ext = "";
        
        int dot = base.lastIndexOf('.');
        if ( dot >= 0)
          {
          ext = base.substring( dot );
          base = base.substring(0, dot);
          }
        
        return new File( getLogDirectory(conf), base + ( version > 0  ? "_" + version : "") + ext);
        }
      }
    }

  /**
   * Sets context for toast-log 
   * @param conf PRIMARY/SECONDARY configuration
   * @param context  context of the activity, null if disabled
   */
  private static void setContext( int conf, Context context ) 
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].context = context;
      }
    }

  /**
   * Gets context for toast-log 
   * @param conf PRIMARY/SECONDARY configuration
   * @return context of the activity, null if disabled
   */
  private static Context getContext( int conf ) 
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].context;
      }
    }

  /**
   * Sets limits for log-level 
   * @param conf PRIMARY/SECONDARY configuration
   * @param min lower limit
   * @param max upper limit (  No restriction above NO_LIMIT!)
   */
  private static void setLimits( int conf, int min, int max ) 
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].minLimit = min;
      config[conf].maxLimit = max;
      }
    }

  /**
   * Gets lower limit for log-level
   * @param conf PRIMARY/SECONDARY configuration
   * @return min limit
   */
  private static int getMinLimit( int conf ) 
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].minLimit;
      }
    }

  /**
   * Gets upper limit for log-level
   * @param conf PRIMARY/SECONDARY configuration
   * @return max limit
   */
  private static int getMaxLimit( int conf ) 
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].maxLimit;
      }
    }

  /**
   * Check wether level is enabled
   * @param level messsage level
   * @return true if level is enabled (within limits or above NO_LIMIT)
   */
  private static boolean isLevelEnabled( int level )
    {
    if ( level>= NO_LIMIT )
      return true;
    
    int conf = level % 2;
    
    if ( level < getMinLimit(conf) || level > getMaxLimit(conf) )
      return false;
    
    return true;
    }
  
  /**
   * Sets time stamp enabled for file-log messages
   * @param conf PRIMARY/SECONDARY configuration
   * @param enable true if enabled
   */
  private static void setTimeStampEnabled( int conf, boolean enable )
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].timeStampEnabled = enable;
      }
    }
  
  /**
   * Returns time stamp enabled state (for file-log messages) 
   * @param conf PRIMARY/SECONDARY configuration
   * @return true if enabled
   */
  private static boolean isTimeStampEnabled( int conf )
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].timeStampEnabled;
      }
    }
  
  /**
   * Current time in format "(yy-MM-dd HH:mm:ss.SSS) ".
   * @return current time as formatted string (empty string if disabled)
   */
  private static String timeStamp()
    {
    SimpleDateFormat sdf=new SimpleDateFormat( "yy-MM-dd HH:mm:ss.SSS", Locale.US );  
    return "(" + sdf.format( new Date() ) + ") ";
    }  
  
  /**
   * Current time in format "(yy-MM-dd HH:mm:ss.SSS) ", if enabled in this config.
   * @param conf PRIMARY/SECONDARY configuration
   * @return current time as formatted string (empty string if disabled)
   */
  private static String timeStamp( int conf )
    {
    if ( !isTimeStampEnabled( conf ) )
      return "";
    return timeStamp();
    }  
  
  /**
   * Sets space stamp (class.method) enabled for file-log messages
   * @param conf PRIMARY/SECONDARY configuration
   * @param enable true if enabled
   */
  private static void setSpaceStampEnabled( int conf, boolean enable )
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] ) 
      {
      config[conf].spaceStampEnabled = enable;
      }
    }
  
  /**
   * Returns space stamp enabled state (for file-log messages) 
   * @param conf PRIMARY/SECONDARY configuration
   * @return true if enabled
   */
  private static boolean isSpaceStampEnabled( int conf )
    {
    synchronized ( lock[conf] ) 
      {
      return config[conf].spaceStampEnabled;
      }
    }
  
  /**
   * Current position in (class.method) format, if enabled in this config.
   * @param conf PRIMARY/SECONDARY configuration
   * @return current position (empty string if disabled)
   */
  private static String spaceStamp( int conf )
    {
    if ( !isSpaceStampEnabled( conf ) )
      return "";

    StackTraceElement[] element = new Throwable().getStackTrace();
    
    int index = 1;
    while ( element[index].getFileName().startsWith("Scribe.") )
      index++;
    
    String c = element[index].getClassName();
    int p = c.lastIndexOf('.');
    if ( p >= 0)
      c = c.substring( p+1, c.length());
    return " (" + c + "." + element[index].getMethodName() + ")";
    }
  
  /**
   * Current position in 'package.class.method (thread)' format
   * @return current position
   */
  private static String spaceStamp()
    {
    StackTraceElement[] element = new Throwable().getStackTrace();
    
    int index = 1;
    while ( element[index].getFileName().startsWith("Scribe.") )
      index++;
    
    return 
      element[index].getClassName() + "." +
      element[index].getMethodName() + " (" +
      Thread.currentThread().getName() + ")";
    }
  
  /**
   * Returns to previously saved config settings. Toast-log will be disabled.
   * @param conf PRIMARY/SECONDARY configuration
   * @param state  settings returned by {@link #getAll(int)}
   */
  private static void setAll( int conf, Bundle state )
    {
    activateSecondaryConfig( conf );
    synchronized ( lock[conf] )
      {
      config[conf].enabled = state.getBoolean("ENABLED", true);
      config[conf].debugEnabled = state.getBoolean("DEBUG_ENABLED", true);
      config[conf].logTag = state.getString("LOG_TAG");
      if ( config[conf].logTag == null ) 
        config[conf].logTag = DEFAULT_LOG_TAG;
      config[conf].fileName = state.getString("FILE_NAME");
      if ( config[conf].fileName == null )
        config[conf].fileName = defaultFileName;
      config[conf].directoryName = state.getString("DIRECTORY_NAME");
      if ( config[conf].directoryName == null )
        config[conf].directoryName = DEFAULT_DIRECTORY;
      config[conf].minLimit = state.getInt("MIN_LIMIT", 0);
      config[conf].maxLimit = state.getInt("MAX_LIMIT", NO_LIMIT);
      config[conf].timeStampEnabled = state.getBoolean("TIME_STAMP_ENABLED", true);
      config[conf].spaceStampEnabled = state.getBoolean("SPACE_STAMP_ENABLED", true);
      config[conf].context = null;  // Ezt veszlyes hosszan hasznlni, tltsnl is kikapcsoljuk!
      }
    }

  /**
   * Gets all log config settings. Toast-log will be disabled. 
   * @param conf PRIMARY/SECONDARY configuration
   * @return bundle of primary log config settings
   */
  private static Bundle getAll( int conf )
    {
    Bundle state = new Bundle( 9 );
    
    synchronized ( lock[conf] )
      {
      state.putBoolean("ENABLED", config[conf].enabled);
      state.putBoolean("DEBUG_ENABLED", config[conf].debugEnabled);
      state.putString("LOG_TAG", config[conf].logTag);
      state.putString("FILE_NAME", config[conf].fileName);
      state.putString("DIRECTORY_NAME", config[conf].directoryName);
      state.putInt("MIN_LIMIT", config[conf].minLimit);
      state.putInt("MAX_LIMIT", config[conf].maxLimit);
      state.putBoolean("TIME_STAMP_ENABLED", config[conf].timeStampEnabled);
      state.putBoolean("SPACE_STAMP_ENABLED", config[conf].spaceStampEnabled);
      config[conf].context = null; // Ezt veszlyes hosszan hasznlni, mentsnl is kikapcsoljuk!
      }
    return state;
    }
  

  /************************************************************
   *                                                          *
   *  Intializations                                          *
   *                                                          *
   *  Initialization is not needed.                           *
   *  Configs are stored on application level (static),       *
   *  it is a good idea to reset configs at entry level.      *
   *  (Application, Activity etc.)                            *
   *                                                          *
   ************************************************************/
  
  /**
   * Configs reset to initial values, secondary config is inactivated.
   * Default file name resets to DEFAULT_FILE_NAME.
   */
  public static void init()
    {
    synchronized ( lock[SECONDARY_CONFIG] )
      {
      config[SECONDARY_CONFIG] = primaryConfig;
      }
    
    defaultFileName = DEFAULT_FILE_NAME; // defaultFileName is volatile !!
    resetAll( PRIMARY_CONFIG );
    }

  /**
   * Configs reset to initial values, secondary config is inactivated.
   * Default file name will be the package name.
   */
  public static void init( Context context )
    {
    synchronized ( lock[SECONDARY_CONFIG] )
      {
      config[SECONDARY_CONFIG] = primaryConfig;
      }
    
    setDefaultFileName( context ); // defaultFileName is volatile !!
    resetAll( PRIMARY_CONFIG );
    }
    
  
  /************************************************************
   *                                                          *
   *  User control of PRIMARY config                          *
   *                                                          *
   *  Primary config is used for ALL message levels           *
   *  before activation of a secondary config                 *
   *  Primary config is used only for EVEN message leves      *
   *  after activation of secondary config                    *
   *                                                          *
   ************************************************************/

  /**
   * Returns log config. Toast-log will be disabled.
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @return bundle of log config settings
   * <p>
   * @see #setConfig(Bundle)
   * @see #getSecondaryConfig()
   */
  public static Bundle getConfig()
    {
    return getAll( PRIMARY_CONFIG );
    }
  
  /**
   * Returns to previously saved config settings. Toast-log will be disabled.
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @param state  settings returned by {@link #getState()}
   * <p>
   * @see #getConfig()
   * @see #setSecondaryConfig()
   */
  public static void setConfig( Bundle state )
    {
    setAll( PRIMARY_CONFIG, state );
    }

  /** 
   * Enables logging (main "switch")
    * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void enable()
    {
    setEnabled( PRIMARY_CONFIG, true );
    }

  /** 
   * Disables logging (main "switch") 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void disable()
    {
    setEnabled( PRIMARY_CONFIG, false );
    }
    
  /** 
   * Enables debug-logging
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void enableDebug()
    {
    setDebugEnabled( PRIMARY_CONFIG, true );
    }

  /** 
   * Disables debug-logging 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void disableDebug()
    {
    setDebugEnabled( PRIMARY_CONFIG, false );
    }

  /** 
   * Enables logging to system-log
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @param logTag  tag appearing in system log
   */
  public static void enableSysLog( String logTag ) 
    {
    setSysLog( PRIMARY_CONFIG, logTag );
    }

  /** 
   * Enables logging to system-log with default tag 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void enableSysLog() 
    {
    setSysLog( PRIMARY_CONFIG, DEFAULT_LOG_TAG );
    }

  /** 
   * Disables logging to system-log 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void disableSysLog()
    {
    setSysLog( PRIMARY_CONFIG, null );
    }
  
  /** 
   * Enables logging to file log under default file name
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @param fileName  log-file's name
   */
  public static void enableFileLog( String fileName ) 
    {
    setFileName( PRIMARY_CONFIG, fileName );
    }

  /** 
   * Enables logging to file log under default file name 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void enableFileLog() 
    {
    setFileName( PRIMARY_CONFIG, defaultFileName );
    }

  /** 
   * Disables logging to file log 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void disableFileLog()
    {
    setFileName( PRIMARY_CONFIG, null );
    }
  
  /** 
   * Enables logging to toast log
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @param context  context of the activity
   */
  public static void enableToastLog( Context context ) 
    {
    setContext( PRIMARY_CONFIG, context );
    }

  /** 
   * Disables logging to toast log 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void disableToastLog()
    {
    setContext( PRIMARY_CONFIG, null );
    }

  /**
   * Sets directory path (on sd-card) for log-file
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @param directoryName  path of logging directory
   */
  public static void setDirectoryName( String directoryName )
    {
    setDirectoryName( PRIMARY_CONFIG, directoryName );
    }
  
  /**
   * Sets lower and upper limit. Messages outside these limits will be disabled.
   * (No restriction for limits above NO_LIMIT!)
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @param maxLimit 
   */
  public static void setLimits( int min, int max )
    {
    setLimits( PRIMARY_CONFIG, min, max);
    }

  /**
   * Sets lower limit and clears upper limit. 
   * Messages below lower limit will be disabled.
   * @param minLimit lower limit
   */
  public static void setMinLimit( int minLimit )
    {
    setLimits( PRIMARY_CONFIG, minLimit, NO_LIMIT );
    }

  /**
   * Sets upper limit and clears lower limit. Messages above upper limit will be disabled.
   * (No restriction for limits above NO_LIMIT!)
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   * @param maxLimit upper limit 
   */
  public static void setMaxLimit( int maxLimit )
    {
    setLimits( PRIMARY_CONFIG, 0, maxLimit );
    }
  
  /**
   * Resets lower and upper limit to their original values. No messages will be limited.
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void resetLimits( )
    {
    setLimits( PRIMARY_CONFIG, 0, NO_LIMIT );
    }

  /** 
   * Enables time stamp in file-log 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void enableTimeStamp()
    {
    setTimeStampEnabled( PRIMARY_CONFIG, true );
    }

  /**
   * Disables time stamp in file-log 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void disableTimeStamp()
    {
    setTimeStampEnabled( PRIMARY_CONFIG, false );
    }

  /** 
   * Enables space stamp (class.method) in file-log 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void enableSpaceStamp()
    {
    setSpaceStampEnabled( PRIMARY_CONFIG, true );
    }

  /** 
   * Disables space stamp (class.method) in file-log 
   * PRIMARY Config - valid for ALL/EVEN levels after activation of sec. config 
   */
  public static void disableSpaceStamp()
    {
    setSpaceStampEnabled( PRIMARY_CONFIG, false );
    }

  
  /************************************************************
   *                                                          *
   *  User control of Secondary config                        *
   *                                                          *
   *  Secondary config is used for ODD message levels         *
   *  Secondary config will be active only                    *
   *  after the first modification of sec. config values      *
   *                                                          *
   ************************************************************/

  /**
   * Returns log config. Toast-log will be disabled. 
   * SECONDARY Config - valid for ALL/ODD levels after activation of sec. config 
   * @return bundle of log config settings
   * <p>
   * @see #getConfig(Bundle)
   * @see #setConfigSecondary()
   */
  public static Bundle getConfigSecondary()
    {
    return getAll( SECONDARY_CONFIG );
    }
  
  /**
   * Returns to previously saved config settings. Toast-log will be disabled.
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param state  settings returned by {@link #getConfigSecondary()}
   * <p>
   * @see #setConfig()
   * @see #getConfigSecondary()
   */
  public static void setConfigSecondary( Bundle state )
    {
    setAll( SECONDARY_CONFIG, state );
    }

  /** 
   * Enables logging (main "switch") 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void enableSecondary()
    {
    setEnabled( SECONDARY_CONFIG, true );
    }

  /** 
   * Disables logging (main "switch") 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void disableSecondary()
    {
    setEnabled( SECONDARY_CONFIG, false );
    }
    
  /** 
   * Enables debug-logging 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void enableDebugSecondary()
    {
    setDebugEnabled( SECONDARY_CONFIG, true );
    }

  /**
   *  Disables debug-logging 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void disableDebugSecondary()
    {
    setDebugEnabled( SECONDARY_CONFIG, false );
    }

  /** 
   * Enables logging to system-log
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param logTag  tag appearing in system log
   */
  public static void enableSysLogSecondary( String logTag ) 
    {
    setSysLog( SECONDARY_CONFIG, logTag );
    }

  /**
   * Enables logging to system-log with default tag 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void enableSysLogSecondary() 
    {
    setSysLog( SECONDARY_CONFIG, DEFAULT_LOG_TAG );
    }

  /**
   * Disables logging to system-log 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void disableSysLogSecondary()
    {
    setSysLog( SECONDARY_CONFIG, null );
    }
  
  /** 
   * Enables logging to file log under default file name
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param fileName  log-file's name
   */
  public static void enableFileLogSecondary( String fileName ) 
    {
    setFileName( SECONDARY_CONFIG, fileName );
    }

  /**
   * Enables logging to file log under default file name
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void enableFileLogSecondary() 
    {
    setFileName( SECONDARY_CONFIG, defaultFileName );
    }

  /** 
   * Disables logging to file log 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void disableFileLogSecondary()
    {
    setFileName( SECONDARY_CONFIG, null );
    }
  
  /** 
   * Enables logging to toast log
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param context  context of the activity
   */
  public static void enableToastLogSecondary( Context context ) 
    {
    setContext( SECONDARY_CONFIG, context );
    }

  /** 
   * Disables logging to toast log 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void disableToastLogSecondary()
    {
    setContext( SECONDARY_CONFIG, null );
    }

  /**
   * Sets directory path (on sd-card) for log-file
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param directoryName  path of logging directory
   */
  public static void setDirectoryNameSecondary( String directoryName )
    {
    setDirectoryName( SECONDARY_CONFIG, directoryName );
    }
  
  /**
   * Sets lower and upper limit. Messages outside these limits will be disabled.
   * (No restriction for limits above NO_LIMIT!)
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param maxLimit 
   */
  public static void setLimitsSecondary( int min, int max )
    {
    setLimits( SECONDARY_CONFIG, min, max);
    }

  /**
   * Sets lower limit and clears upper limit. Messages below lower limit will be disabled.
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param minLimit lower limit
   */
  public static void setMinLimitSecondary( int minLimit )
    {
    setLimits( SECONDARY_CONFIG, minLimit, NO_LIMIT );
    }

  /**
   * Sets upper limit and clears lower limit. Messages above upper limit will be disabled.
   * (No restriction for limits above NO_LIMIT!)
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   * @param maxLimit upper limit 
   */
  public static void setMaxLimitSecondary( int maxLimit )
    {
    setLimits( SECONDARY_CONFIG, 0, maxLimit );
    }
  
  /**
   * Resets lower and upper limit to their original values. No messages will be limited.
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void resetLimitsSecondary( )
    {
    setLimits( SECONDARY_CONFIG, 0, NO_LIMIT );
    }

  /** 
   * Enables time stamp in file-log 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void enableTimeStampSecondary()
    {
    setTimeStampEnabled( SECONDARY_CONFIG, true );
    }

  /** 
   * Disables time stamp in file-log 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void disableTimeStampSecondary()
    {
    setTimeStampEnabled( SECONDARY_CONFIG, false );
    }

  /** 
   * Enables space stamp (class.method) in file-log 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void enableSpaceStampSecondary()
    {
    setSpaceStampEnabled( SECONDARY_CONFIG, true );
    }

  /** 
   * Disables space stamp (class.method) in file-log 
   * SECONDARY Config - valid for ODD levels. Secondary config will be activated 
   */
  public static void disableSpaceStampSecondary()
    {
    setSpaceStampEnabled( SECONDARY_CONFIG, false );
    }
  
  
  /************************************************************
   *                                                          *
   *  Private methods for log messaging                       *
   *                                                          *
   ************************************************************/

  /**
   * Different types of log
   * <ul>
   * <li>TITLE Title log style</li>
   * <li>NOTE Normal log style</li>
   * <li>DEBUG Normal log style, appears only if debug-log is enabled</li>
   * <li>ERROR Error log style</li>
   * <li>LOCUS Information about method, class, file, thread</li>
   * </ul>
   */
  private static enum Type
    {
    TITLE,
    NOTE,
    DEBUG,
    ERROR,
    LOCUS
    };
  
    
  /**
   * Adds text with type to system log, toast log and file log. 
   * Log note appears only if log is enabled and only on enabled log streams.
   * @param type log style 
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  private static String addText( Type type, int level, String text )
    {
    int conf = level % 2; 
    
    if ( !isEnabled(conf) )
      return OFF;
      
    if ( (type == Type.DEBUG || type == Type.LOCUS) && !isDebugEnabled(conf) )
      return OFF;
      
    if ( !isLevelEnabled(level) )
      return OFF;
      
    addTextToSysLog( type, conf, text );
    addTextToToastLog( type, conf, text );
    return addTextToFileLog( type, conf, text );
    }
  
  /**
   * Adds text with type to toast log.
   * Can be sent from any thread, toast will displayed from main thread
   * Log note appears only if logging to toast log is enabled.
   * (Main enable is not checked)
   * @param type log style 
   * @param text log note
   */
  private static void addTextToToastLog( Type type, final int conf, final String text )
    {
    final Context context = getContext(conf);
    
    // Log a kepernyore
    if ( context != null )
      {
      // http://stackoverflow.com/questions/18280012/how-to-replace-the-system-out-with-toasts-inside-a-thread/18280318#18280318
      /*
       * activity.runOnUiThread(new Runnable() 
       *     {
       *     public void run() 
       *       {
       *       Toast.makeText(activity, "Hello", Toast.LENGTH_SHORT).show();
       *       }
       *     });
       */
      
            new Handler( Looper.getMainLooper() ).post( new Runnable() 
              {
              @Override
              public void run() 
                {
                Toast.makeText( context, text, Toast.LENGTH_SHORT).show();
                }
              });
       
      // Toast.makeText( context[0], text, Toast.LENGTH_SHORT ).show();
      }
    }
  
  /**
   * Adds text with type to system log. 
   * Log note appears only if logging to system log is enabled.
   * (Main enable is not checked)
   * @param type log style 
   * @param text log note
   */
  private static void addTextToSysLog( Type type, int conf, String text )
    {
    String logTag = getSysLog(conf);
    
    // Log a syslog-ba
    if ( logTag != null )
      {
      switch (type)
        {
        case TITLE:
          Log.i( logTag, " *** " + text + " ***" );
          break;
        case NOTE:
          Log.i( logTag, text );
          break;
        case DEBUG:
          Log.d( logTag, text );
          break;
        case ERROR:
          Log.e( logTag, text );
          break;
        case LOCUS:
          Log.d( logTag, text );
          break;
        default:
        }
      }
    }
    
  /**
   * Adds text with type to file log. 
   * Log note appears only if logging to file log is enabled.
   * (Main enable is not checked)
   * @param type log style 
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error)
   */
  private static String addTextToFileLog( Type type, int conf, String text )
    {
    File logFile = getLogFile( conf );

    // Log a file-ba
    if ( logFile != null )
      {
      OutputStreamWriter logStream = null;

      try
        {
        logStream = new OutputStreamWriter( new FileOutputStream(logFile, true) );
        switch (type)
          {
        case TITLE:
          logStream.append( SEPARATOR + " *** " + text + " ***" + SEPARATOR + SEPARATOR );
          break;
        case NOTE:
        case DEBUG:
          logStream.append( timeStamp(conf) + text + spaceStamp(conf) + SEPARATOR);
          break;
        case ERROR:
          logStream.append( timeStamp(conf) + "ERROR: " + text + spaceStamp(conf) + SEPARATOR );
          break;
        case LOCUS:
          logStream.append( timeStamp(conf) + text + SEPARATOR );
          break;
          }
        logStream.flush();      
        }
      catch (IOException ioe)
        {
        // A hibat a visszateresi ertek mutatja, DE
        // a bekapcsolas fuggvenyeben a tobbi log is kiadhatja
        String err = LOGFILE_ERROR + ioe.toString();
        
        addTextToToastLog( Type.ERROR, conf, err );
        addTextToSysLog( Type.ERROR, conf, err );
  
        // visszateresben mindig adja
        return err;
        }
      finally
        {
        if (logStream != null)
          {
          try 
            {  
            logStream.close();
            }
          catch (IOException ioe)
            {
            // Ezt a hibt vgkpp nem tudjuk hol jelenteni...
            }
          }
        }
      }
      
    return OK;  
    }
      
  
  /************************************************************
   *                                                          *
   *  Public log messaging methods                            *
   *                                                          *
   ************************************************************/

  /**
   * Adds text as type {@code TITLE} to system log, toast log and file log. 
   * Log note appears only on enabled log streams and only if limit allows it.
   * Primary config is used for EVEN, secondary for ODD levels
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String title(int level, String text)
    {
    return addText( Type.TITLE, level, text );
    }
    
  /**
   * Adds text as type {@code NOTE} to system log, toast log and file log. 
   * Log note appears only on enabled log streams, and only if limit allows it.
   * Primary config is used for EVEN, secondary for ODD levels
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String note(int level, String text)
    {
    return addText( Type.NOTE, level, text );
    }
    
  /**
   * Adds text as type {@code DEBUG} to system log, toast log and file log. 
   * Log note appears only if debug-log is enabled and only on enabled log streams, 
   * and only if limit allows it.
   * Primary config is used for EVEN, secondary for ODD levels
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String debug(int level, String text)
    {
    return addText( Type.DEBUG, level, text );
    }

  /**
   * Adds text as type {@code ERROR} to system log, toast log and file log. 
   * Log note appears only on enabled log streams, and only if limit allows it.
   * Primary config is used for EVEN, secondary for ODD levels
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String error(int level, String text)
    {
    return addText( Type.ERROR, level, text );
    }
  
  /**
   * Adds text as type {@code METHOD} to system log, toast log and file log. 
   * Log note appears only on enabled log streams, and only if limit allows it.
   * Primary config is used for EVEN, secondary for ODD levels
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String locus( int level )
    {
    return addText( Type.LOCUS, level, "@ " + spaceStamp() );
    }
    
  // Public methods without limit check
  
  /**
   * Adds text as type {@code TITLE} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Primary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   * @see #checkLogFileLength()
   */
  public static String title(String text)
    {
    return title( NO_LIMIT, text);
    }
    
  /**
   * Adds text as type {@code NOTE} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Primary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String note(String text)
    {
    return note( NO_LIMIT, text);
    }
    
  /**
   * Adds text as type {@code DEBUG} to system log, toast log and file log. 
   * Log note appears only if debug-log is enabled and only on enabled log streams.
   * No limit check.
   * Primary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String debug(String text)
    {
    return debug( NO_LIMIT, text);
    }

  /**
   * Adds text as type {@code ERROR} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Primary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String error(String text)
    {
    return error( NO_LIMIT, text);
    }

  /**
   * Adds text as type {@code METHOD} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Primary config is used.
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String locus()
    {
    return locus( NO_LIMIT );
    }

  // Public methods for Secondary config without limit check
  
  /**
   * Adds text as type {@code TITLE} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Secondary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   * @see #checkLogFileLength()
   */
  public static String title_secondary(String text)
    {
    return title( NO_LIMIT_SECONDARY, text);
    }
    
  /**
   * Adds text as type {@code NOTE} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Secondary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String note_secondary(String text)
    {
    return note( NO_LIMIT_SECONDARY, text);
    }
    
  /**
   * Adds text as type {@code DEBUG} to system log, toast log and file log. 
   * Log note appears only if debug-log is enabled and only on enabled log streams.
   * No limit check.
   * Secondary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String debug_secondary(String text)
    {
    return debug( NO_LIMIT_SECONDARY, text);
    }

  /**
   * Adds text as type {@code ERROR} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Secondary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String error_secondary(String text)
    {
    return error( NO_LIMIT_SECONDARY, text);
    }

  /**
   * Adds text as type {@code METHOD} to system log, toast log and file log. 
   * Log note appears only on enabled log streams. No limit check.
   * Secondary config is used.
   * @param text log note
   * @return message ({@link #OK}, {@link #OFF} or error) from file-log
   */
  public static String locus_secondary(String text)
    {
    return locus( NO_LIMIT_SECONDARY );
    }

  
  /************************************************************
   *                                                          *
   *  Private methods for log file operations                 *
   *                                                          *
   ************************************************************/
  
  /**
   * Deletes log file.
   * Works only if logging and file logging are enabled.
   * Result is logged on system log and toast log (logs - even file log! - should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   */
  private static String clear( int conf )
    {
    conf = conf % 2; // Just for security - public methods
    
    if ( !isEnabled(conf) )
      return OFF;

    File logFile = getLogFile( conf );
    String err;

    if ( logFile == null )
      return OFF;
    
    if ( logFile.delete() )
      {
      err = "<" + logFile.getName() + "> cleared.";
      
      // sikeres torlest a tobbi logon is jelezzuk
      addTextToToastLog( Type.NOTE, conf, err );
      addTextToSysLog( Type.NOTE, conf, err );

      return OK;
      }
      
    err = "Cannot clear <" + logFile.getName() + ">!";
    
    // Sikertelenseg eseten is jelzunk
    addTextToToastLog( Type.ERROR, conf, err );
    addTextToSysLog( Type.ERROR, conf, err );

    return LOGFILE_ERROR + err;
    }

  /**
   * Checks the length of the log file.
   * Works only if logging and file logging are enabled.
   * If file length is longer than {@link MAX_LOGFILE_LENGTH} then 
   * old logfile is archived, and a new one will be created.
   * The maximum number of archived log files is definied in {@link MAX_LOGFILE}.
   * The oldest log file will be deleted after reaching this limit. 
   * Result is logged on system log and toast log (logs - even file log! - should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   * @see #title(String)
   */
  private static String checkLogFileLength( int conf )
    {
    if ( !isEnabled(conf) )
      return OFF;

    File logFile = getLogFile(conf);
    String err;

    if ( logFile == null )
      return OFF;

    boolean ok = true;
    
    FileChannel logChannel = null;
    FileLock lock = null;
    
    try
      {
      logChannel = new RandomAccessFile( logFile, "rw" ).getChannel();
      lock = logChannel.lock();
      
      if ( logFile.length() > MAX_LOGFILE_LENGTH )
        {
        File nextFile;
        File prevFile;
        
        for (int cnt = MAX_LOGFILE; cnt > 0; cnt--)
          {
          nextFile = getLogFile( conf, cnt );
          prevFile = getLogFile( conf, cnt-1 );
          if ( nextFile.exists() )
            ok &= nextFile.delete();
          if ( prevFile.exists() )
            ok &= prevFile.renameTo( nextFile );
          }
        }
      else
        {
        return OK; // No changes needed
        }
      }
    catch (IOException e)
      {
      ok = false; // Exception could occur only in lock!
      }
    finally
      {
      if ( lock != null )
        {
        try
          {
          lock.release();
          } 
        catch (IOException e)
          {
          // Ezt a hibt vgkpp nem tudjuk hol jelenteni...
          }
        }
      if ( logChannel != null)
        {
        try
          {
          logChannel.close();
          } 
        catch (IOException e)
          {
          // Ezt a hibt vgkpp nem tudjuk hol jelenteni...
          }
        }
      }
    
    if ( ok )
      {
      err = "((Log file length check: new <" + logFile.getName() + "> was created.))";
          
      // sikert a tobbi logon is jelezzuk
      addTextToToastLog( Type.NOTE, conf, err );
      addTextToSysLog( Type.NOTE, conf, err );

      return OK;
      }

    err = "((Log file length check: unable to create new <" + logFile.getName() + ">!))";
    
    // Sikertelenseg eseten is jelzunk
    addTextToToastLog( Type.ERROR, conf, err );
    addTextToSysLog( Type.ERROR, conf, err );

    return LOGFILE_ERROR + err;
    }
    

  /************************************************************
   *                                                          *
   *  Public methods for log file operations                  *
   *                                                          *
   ************************************************************/
  
  /**
   * Deletes log file - PRIMARY CONFIG.
   * Works only if logging and file logging are enabled.
   * Result is logged on system log and toast log (logs - even file log! - should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   */
  public static String clear()
    {
    return clear( PRIMARY_CONFIG );
    }
  
  /**
   * Checks the length of the log file - PRIMARY CONFIG.
   * Works only if logging and file logging are enabled.
   * If file length is longer than {@link MAX_LOGFILE_LENGTH} then 
   * old logfile is archived, and a new one will be created.
   * The maximum number of archived log files is definied in {@link MAX_LOGFILE}.
   * The oldest log file will be deleted after reaching this limit. 
   * Result is logged on system log and toast log (logs - even file log! - should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   * @see #title(String)
   */
  public static String checkLogFileLength()
    {
    return checkLogFileLength( PRIMARY_CONFIG );
    }
  
  /**
   * Deletes log file - SECONDARY CONFIG.
   * Works only if logging and file logging are enabled.
   * Result is logged on system log and toast log (logs - even file log! - should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   */
  public static String clear_secondary()
    {
    return clear( SECONDARY_CONFIG );
    }
  
  /**
   * Checks the length of the log file - SECONDARY CONFIG.
   * Works only if logging and file logging are enabled.
   * If file length is longer than {@link MAX_LOGFILE_LENGTH} then 
   * old logfile is archived, and a new one will be created.
   * The maximum number of archived log files is definied in {@link MAX_LOGFILE}.
   * The oldest log file will be deleted after reaching this limit. 
   * Result is logged on system log and toast log (logs - even file log! - should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   * @see #title(String)
   */
  public static String checkLogFileLength_secondary()
    {
    return checkLogFileLength( SECONDARY_CONFIG );
    }
  
  
  /************************************************************
   *                                                          *
   *  System log controls - works only with PRIMARY config    *
   *                                                          *
   ************************************************************/
  
  /**
   * Dumps full system log to file log.
   * Works only if logging and file logging are enabled.
   * Result is logged on system log and toast log (logs - even file log! - should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   */
  public static String dumpSysLog()
    {
    int conf = PRIMARY_CONFIG;
    
    if ( !isEnabled(conf) ) 
      return OFF;

    File logFile = getLogFile(conf);
      
    if ( logFile == null )
      return OFF;
    
    OutputStreamWriter logStream = null;
    BufferedReader bufferedReader = null;
    Process process = null;
    String err;
    
    try
      {    
      process = Runtime.getRuntime().exec("logcat -d -v time");
      bufferedReader = new BufferedReader( new InputStreamReader( process.getInputStream() ));      

      logStream = new OutputStreamWriter( new FileOutputStream(logFile, true) );
      
      String line;
      logStream.append( SEPARATOR + "--- SYSTEM LOG DUMP at " + timeStamp(conf) + "---" + SEPARATOR + SEPARATOR);
      while ( (line = bufferedReader.readLine()) != null )
        {
        logStream.append(line + SEPARATOR);
        }
      logStream.append(SEPARATOR + "--- END OF SYSTEM LOG ---" + SEPARATOR + SEPARATOR);      
      logStream.flush();
      
      err = "<" + logFile.getName() + "> system log dump ready";
        
      // Sikeres befejezes
      addTextToToastLog( Type.NOTE, conf, err );
      addTextToSysLog( Type.NOTE, conf, err);
      
      return OK;
      }
    catch (IOException ioe)
      {
      err = LOGFILE_ERROR + ioe.toString();
        
      // Sikertelenseg
      addTextToToastLog( Type.ERROR, conf, err );
      addTextToSysLog( Type.ERROR, conf, err );
        
      return err;
      }
    finally
      {
      if (logStream != null)
        {
        try 
          {  
          logStream.close();
          }
        catch (IOException ioe)
          {
          // Ezt a hibt vgkpp nem tudjuk hol jelenteni...
          }
        }
      if (bufferedReader != null)
        {
        try 
          {  
          bufferedReader.close();
          }
        catch (IOException ioe)
          {
          // Ezt a hibt vgkpp nem tudjuk hol jelenteni...
          }
        }
      if (process != null)
        {
        process.destroy();
        }
      }
    }  
    
    
  /**
   * Clears system log.
   * Works only if logging is enabled.
   * Result is logged on file log and toast log (logs should be enabled!)  
   * @return result: ({@link #OK}, {@link #OFF} or error)
   */
  public static String clearSysLog( )
    {
    int conf = PRIMARY_CONFIG;
    
    if ( !isEnabled(conf) ) 
      return OFF;
    
    Process process = null;
    String err;
    
    try
      {    
      process = Runtime.getRuntime().exec("logcat -c");
    
      err = "System log cleared.";
      
      // Siker
      addTextToToastLog( Type.NOTE, conf, err );
      addTextToFileLog( Type.NOTE, conf, err );
      
      return OK;
      }
    catch (IOException ioe)
      {
      err = "SYSLOG ERROR: " + ioe.toString();
        
      // sikertelen
      addTextToToastLog( Type.ERROR, conf, err );
      addTextToFileLog( Type.ERROR, conf, err );
      
      return err;
      }
    finally
      {
      if (process != null)
        {
        process.destroy();
        }
      }
    }

  
  /************************************************************
   *                                                          *
   *  Uncaught exceptions                                     *
   *                                                          *
   ************************************************************/
  
  /** System's default exception handler */
  private static UncaughtExceptionHandler defaultUncaughtExceptionHandler = null;
  
  /** 
   * Uncaught exceptions will be catched. 
   * If enabled, exceptions will be logged on PRIMARY file-log.
   * After logging, exceptions are given to the default system handler,
   * which dumps it to the system log, and finishes our program
   * <p> 
   * Uncaught exceptions log is not locked. Synchronization could be performed on an outer lock, 
   * to prevent mixed logs. But two exceptions from different threads at the same time can be a rarity.
   * Log-lines are mixed with other file-logs, but this is a feature, not a bug! 
   * (And could be avoided by synchronization of each file-log writes.)
   */
  public static void logUncaughtExceptions()
    {
    if ( defaultUncaughtExceptionHandler != null )
      return; // already set!
    
    defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
    
    Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() 
        {
            @Override
            public void uncaughtException(Thread thread, Throwable ex) 
              {
              int conf = PRIMARY_CONFIG;
              
              logUncaughtException( conf, thread, ex );
              
                // re-throw critical exception further to the os (important)
                defaultUncaughtExceptionHandler.uncaughtException(thread, ex);
              }
            });
    
    }

  /**
   * Eventually log Thread and Throwable to file log.
   * @param conf PRIMARY/SECONDARY config. Now only primary is used.
   * @param thread thread where the exception occured
   * @param ex exception
   */
  private static void logUncaughtException( int conf, Thread thread, Throwable ex)
    {
    if ( !isEnabled(conf) ) 
      return;

    File logFile = getLogFile(conf);
      
    if ( logFile == null )
      return;
    
    OutputStreamWriter logStream = null;
    
    try
      {    
      logStream = new OutputStreamWriter( new FileOutputStream(logFile, true) );
      
      logStream.append( SEPARATOR + "*** Uncaught exception: " + thread.getName() + " " + 
          timeStamp(conf) + " ***" + SEPARATOR + SEPARATOR);

      logExceptionHistory( logStream, ex );
      
      logStream.append( SEPARATOR + "*** End of uncaught exception ***" + SEPARATOR + SEPARATOR);      
      logStream.flush();
      }
    catch (IOException ioe)
      {
      // Can not handle these exceptions 
      }
    finally
      {
      if (logStream != null)
        {
        try 
          {  
          logStream.close();
          }
        catch (IOException ioe)
          {
          // Can not handle these exceptions 
          }
        }
      }
    }

  /**
   * Recursively logs exceptions and their stack-traces.
   * Private method for logUncaughtException.
   * Exceptions are logged in different order than in system-log!
   * There are no abbreviations, all stack-traces are printed!   
   * @param logStream where to log
   * @param ex exception to log
   * @throws IOException exception is thrown, but at the end this is not reported
   */
  private static void logExceptionHistory(OutputStreamWriter logStream, Throwable ex) throws IOException
    {
    if ( ex.getCause() != null )
      {
      logExceptionHistory( logStream, ex.getCause() );
      logStream.append( SEPARATOR + "*** Caused:" + SEPARATOR );
      }
    
    logStream.append( "*** " + ex.getClass().getName() + SEPARATOR + 
      "  >" + ex.getMessage() + "<" + SEPARATOR );
    
    for ( StackTraceElement element: ex.getStackTrace() )
      {
      logStream.append( " @ " + element.getClassName() + "." + element.getMethodName() 
          + " (" + element.getFileName() + ( element.getLineNumber()<0 ? "" : "/" + element.getLineNumber() ) + ")" 
          + SEPARATOR );
      }
    }
    
  }




Java Source Code List

digitalgarden.bestboard.BoardParser.java
digitalgarden.bestboard.Board.java
digitalgarden.bestboard.MainActivity.java
digitalgarden.bestboard.Roller.java
digitalgarden.bestboard.TokenizerTest.java
digitalgarden.magicmerlin.scribe.Scribe.java
digitalgarden.magicmerlin.utils.Keyboard.java
digitalgarden.magicmerlin.utils.Rest.java
digitalgarden.magicmerlin.utils.Screen.java
digitalgarden.magicmerlin.utils.Tokenizer.java