Package winterwell.utils.reporting

Source Code of winterwell.utils.reporting.Log

package winterwell.utils.reporting;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.logging.Level;

import winterwell.utils.Environment;
import winterwell.utils.Key;
import winterwell.utils.Printer;
import winterwell.utils.Utils;

public final class Log {

  public static enum KErrorPolicy {
    /** Keep the error as is (throw an exception if this is not possible) */
    ACCEPT,
    /** Remove the file or whatever was to blame */
    DELETE_CAUSE,
    /**
     * There is no point in going on.
     *
     * @deprecated You rarely want this.
     */
    DIE,
    /** There is no error here. This never happened. */
    IGNORE,
    /** Report to log or user. */
    REPORT,
    /** Return null _or a default value_ in place of whatever. */
    RETURN_NULL,
    /** Raise hell. */
    THROW_EXCEPTION
  }

  /**
   * An extra string which can be accessed by log listeners. Added to
   * {@link LogFile}s. Usage: e.g. a high-level process wishes to include info
   * in low-level reports.
   */
  private static final Key<String> ENV_CONTEXT_MESSAGE = new Key<String>(
      "Log.context");

  private static final Collection<String> ignoredTags = new ArrayList();

  private static ILogListener[] listeners = new ILogListener[0];

  /**
   * Maximum length (in chars) of a single log report: 5k
   */
  private static final int MAX_LENGTH = 1024 * 5;

  private static final Key<Level> MIN_LEVEL = new Key<Level>("Log.minLevel");

  // private static final Set<Thread> blacklist = new ArraySet<Thread>();
  // just an idea...
  // public static void setBlacklist(Thread thread, boolean isIgnored) {
  // if (isIgnored) blacklist.add(thread);
  // else blacklist.remove(thread);
  // }

  // Add a simple console output listener
  static {
    addListener(new ILogListener() {
      @Override
      public void listen(Report report) {
        Printer.out(Environment.get().get(Printer.INDENT)
            + report.getMessage());
      }
    });
  }

  /**
   * Listen for log reports
   *
   * @param listener
   */
  public static synchronized void addListener(ILogListener listener) {
    assert listener != null;
    for (ILogListener l : listeners) {
      if (l.equals(listener))
        return;
    }
    listeners = Arrays.copyOf(listeners, listeners.length + 1);
    listeners[listeners.length - 1] = listener;
  }

  /**
   * @return extra contextual message, or "" if unset
   */
  public static String getContextMessage() {
    String cm = Environment.get().get(Log.ENV_CONTEXT_MESSAGE);
    return cm == null ? "" : cm;
  }

  /**
   * get the *default* minimum level to report events. Applies across all
   * threads.
   */
  public static Level getDefaultLevel() {
    // FIXME this is only the default if no-one sets the thread-local!
    return Environment.get().get(MIN_LEVEL);
  }

  public static void info(Object message) {
    info(null, message);
  }

  /**
   * Provide an informational message
   *
   * @param message
   */
  public static void info(String tag, Object message) {
    assert !(message instanceof Throwable);
    report(tag, message, Level.INFO);
  }

  public static synchronized void removeListener(ILogListener listener) {
    ArrayList<ILogListener> ls = new ArrayList(Arrays.asList(listeners));
    ls.remove(listener);
    listeners = ls.toArray(new ILogListener[0]);
  }

  public static void report(Object msg) {
    if (!(msg instanceof Throwable)) {
      report(msg, Level.WARNING);
    } else {
      report((Throwable) msg);
    }
  }

  public static void report(Object msg, Level error) {
    report(null, msg, error);
  }

  /**
   * This is the "master" version of this method (to which the others delegate
   * - so perhaps that makes it more the servant method?).
   * <p>
   * It should never throw an exception. Any exceptions will be swallowed.
   *
   * @param tag
   *            Inspired by Android's LogCat. The tag is a rough
   *            classification on the report, which allows for
   *            simple-but-effective filtering. Can be null
   * @param msg
   * @param error
   */
  public static void report(String tag, Object msg, Level error) {
    // Ignore?
    Level minLevel = Environment.get().get(MIN_LEVEL);
    if (minLevel != null && minLevel.intValue() > error.intValue())
      return;
    if (ignoredTags.contains(tag))
      return;

    String msgText = Printer.toString(msg);
    // Guard against giant objects getting put into log, which is almost
    // certainly a careless error
    if (msgText.length() > MAX_LENGTH) {
      msgText = msgText.substring(0, MAX_LENGTH / 2)
          + "... (message is too long for Log!)";
      System.err.println(new IllegalArgumentException(
          "Log message too long: " + msgText));
    }
    Report report = new Report(tag, msgText, error);
    // We were getting ConcurrentModificationExceptions on startup for Small
    // Town.
    // This hack should avoid that.
    // Commented out by JH -- can't see why this would make any difference.
    // List<ILogListener> listeners2 = new
    // ArrayList<ILogListener>(listeners);
    for (ILogListener listener : listeners) {
      try {
        listener.listen(report);
      } catch (Exception ex) {
        // swallow if something goes wrong
        ex.printStackTrace();
      } catch (AssertionError ex) {
        ex.printStackTrace();
      }
    }
  }

  public static void report(Throwable ex) {
    report(Printer.toString(ex, true), Level.SEVERE);
  }

  public static void setContextMessage(String message) {
    Environment.get().put(Log.ENV_CONTEXT_MESSAGE, message);
  }

  /**
   * Set *default* minimum level to report events. Applies across all threads.
   *
   * @param level
   */
  public static void setDefaultLevel(Level level) {
    Environment.putDefault(MIN_LEVEL, level);
  }

  /**
   *
   * @param tag
   * @param ignore
   *            true => ignore
   */
  public static void setIgnoreTag(String tag, boolean ignore) {
    if (ignore) {
      ignoredTags.add(tag);
    } else {
      while (ignoredTags.contains(tag)) {
        ignoredTags.remove(tag);
      }
    }
  }

  /**
   * Minimum level to report events. Only sets for this thread! TODO should
   * this be on a per-listener basis?
   *
   * @param level
   */
  public static void setLevel(Level level) {
    Environment.get().put(MIN_LEVEL, level);
  }

  /**
   * For pain-level debugging.
   * <p>
   * This prints out (via .report()):<br>
   * class.method(file:linenumber): objects<br>
   * It does so in a format which can be copied-and-pasted into Eclipse's Java
   * Stack Trace Console, where it will gain a link to the line of code.
   *
   * @param objects
   *            These will be printed out.
   */
  public static void trace(Object... objects) {
    StackTraceElement caller = Utils.getCaller();
    Log.report(caller + ":  " + Printer.toString(objects), Level.FINEST);
  }

  /**
   * You cannot pass Throwable objects to this method
   *
   * @param string
   */
  public static void warn(Object message) {
    assert !(message instanceof Throwable);
    report(message, Level.WARNING);
  }

  /**
   * Does nothing. Provides an object if you need one - but all the methods
   * are static.
   */
  public Log() {
    // does nothing
  }

}
TOP

Related Classes of winterwell.utils.reporting.Log

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.