All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowLog Maven / Gradle / Ivy

package org.robolectric.shadows;

import android.util.Log;
import com.google.common.base.Ascii;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Supplier;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;

/** Controls the behavior of {@link android.util.Log} and provides access to log messages. */
@Implements(Log.class)
public class ShadowLog {
  public static PrintStream stream;

  private static final int EXTRA_LOG_LENGTH = "l/: \n".length();

  private static final Map> logsByTag = Collections.synchronizedMap(new
      HashMap>());
  private static final Queue logs = new ConcurrentLinkedQueue<>();
  private static final Map tagToLevel = Collections.synchronizedMap(new
      HashMap());

  /**
   * Whether calling {@link Log#wtf} will throw {@link TerribleFailure}. This is analogous to
   * Android's {@link android.provider.Settings.Global#WTF_IS_FATAL}. The default value is false to
   * preserve existing behavior.
   */
  private static boolean wtfIsFatal = false;

  /** Provides string that will be used as time in logs. */
  private static Supplier timeSupplier;

  @Implementation
  protected static int e(String tag, String msg) {
    return e(tag, msg, null);
  }

  @Implementation
  protected static int e(String tag, String msg, Throwable throwable) {
    return addLog(Log.ERROR, tag, msg, throwable);
  }

  @Implementation
  protected static int d(String tag, String msg) {
    return d(tag, msg, null);
  }

  @Implementation
  protected static int d(String tag, String msg, Throwable throwable) {
    return addLog(Log.DEBUG, tag, msg, throwable);
  }

  @Implementation
  protected static int i(String tag, String msg) {
    return i(tag, msg, null);
  }

  @Implementation
  protected static int i(String tag, String msg, Throwable throwable) {
    return addLog(Log.INFO, tag, msg, throwable);
  }

  @Implementation
  protected static int v(String tag, String msg) {
    return v(tag, msg, null);
  }

  @Implementation
  protected static int v(String tag, String msg, Throwable throwable) {
    return addLog(Log.VERBOSE, tag, msg, throwable);
  }

  @Implementation
  protected static int w(String tag, String msg) {
    return w(tag, msg, null);
  }

  @Implementation
  protected static int w(String tag, Throwable throwable) {
    return w(tag, null, throwable);
  }

  @Implementation
  protected static int w(String tag, String msg, Throwable throwable) {
    return addLog(Log.WARN, tag, msg, throwable);
  }

  @Implementation
  protected static int wtf(String tag, String msg) {
    return wtf(tag, msg, null);
  }

  @Implementation
  protected static int wtf(String tag, String msg, Throwable throwable) {
    addLog(Log.ASSERT, tag, msg, throwable);
    if (wtfIsFatal) {
      throw new TerribleFailure(msg, throwable);
    }
    return 0;
  }

  /** Sets whether calling {@link Log#wtf} will throw {@link TerribleFailure}. */
  public static void setWtfIsFatal(boolean fatal) {
    wtfIsFatal = fatal;
  }

  /** Sets supplier that can be used to get time to add to logs. */
  public static void setTimeSupplier(Supplier supplier) {
    timeSupplier = supplier;
  }

  @Implementation
  protected static boolean isLoggable(String tag, int level) {
    synchronized (tagToLevel) {
      if (tagToLevel.containsKey(tag)) {
        return level >= tagToLevel.get(tag);
      }
    }
    return level >= Log.INFO;
  }

  @Implementation
  protected static int println_native(int bufID, int priority, String tag, String msg) {
    addLog(priority, tag, msg, null);
    int tagLength = tag == null ? 0 : tag.length();
    int msgLength = msg == null ? 0 : msg.length();
    return EXTRA_LOG_LENGTH + tagLength + msgLength;
  }

  /**
   * Sets the log level of a given tag, that {@link #isLoggable} will follow.
   * @param tag A log tag
   * @param level A log level, from {@link android.util.Log}
   */
  public static void setLoggable(String tag, int level) {
    tagToLevel.put(tag, level);
  }

  private static int addLog(int level, String tag, String msg, Throwable throwable) {
    String timeString = null;
    if (timeSupplier != null) {
      timeString = timeSupplier.get();
    }

    if (stream != null) {
      logToStream(stream, timeString, level, tag, msg, throwable);
    }

    LogItem item = new LogItem(timeString, level, tag, msg, throwable);
    Queue itemList;

    synchronized (logsByTag) {
      if (!logsByTag.containsKey(tag)) {
        itemList = new ConcurrentLinkedQueue<>();
        logsByTag.put(tag, itemList);
      } else {
        itemList = logsByTag.get(tag);
      }
    }

    itemList.add(item);
    logs.add(item);

    return 0;
  }

  protected static char levelToChar(int level) {
    final char c;
    switch (level) {
      case Log.ASSERT: c = 'A'; break;
      case Log.DEBUG:  c = 'D'; break;
      case Log.ERROR:  c = 'E'; break;
      case Log.WARN:   c = 'W'; break;
      case Log.INFO:   c = 'I'; break;
      case Log.VERBOSE:c = 'V'; break;
      default:         c = '?';
    }
    return c;
  }

  private static void logToStream(
      PrintStream ps, String timeString, int level, String tag, String msg, Throwable throwable) {

    String outputString;
    if (timeString != null && timeString.length() > 0) {
      outputString = timeString + " " + levelToChar(level) + "/" + tag + ": " + msg;
    } else {
      outputString = levelToChar(level) + "/" + tag + ": " + msg;
    }

    ps.println(outputString);
    if (throwable != null) {
      throwable.printStackTrace(ps);
    }
  }

  /**
   * Returns ordered list of all log entries.
   *
   * @return List of log items
   */
  public static ImmutableList getLogs() {
    return ImmutableList.copyOf(logs);
  }

  /**
   * Returns ordered list of all log items for a specific tag.
   *
   * @param tag The tag to get logs for
   * @return The list of log items for the tag or an empty list if no logs for that tag exist.
   */
  public static ImmutableList getLogsForTag(String tag) {
    Queue logs = logsByTag.get(tag);
    return logs == null ? ImmutableList.of() : ImmutableList.copyOf(logs);
  }

  /** Clear all accumulated logs. */
  public static void clear() {
    reset();
  }

  @Resetter
  public static void reset() {
    logs.clear();
    logsByTag.clear();
    tagToLevel.clear();
    wtfIsFatal = false;
  }

  @SuppressWarnings("CatchAndPrintStackTrace")
  public static void setupLogging() {
    String logging = System.getProperty("robolectric.logging");
    if (logging != null && stream == null) {
      PrintStream stream = null;
      if (Ascii.equalsIgnoreCase("stdout", logging)) {
        stream = System.out;
      } else if (Ascii.equalsIgnoreCase("stderr", logging)) {
        stream = System.err;
      } else {
        try {
          final PrintStream file = new PrintStream(new FileOutputStream(logging), true);
          stream = file;
          Runtime.getRuntime()
              .addShutdownHook(
                  new Thread() {
                    @Override
                    public void run() {
                      file.close();
                    }
                  });
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      ShadowLog.stream = stream;
    }
  }

  /** A single log item. */
  public static final class LogItem {
    public final String timeString;
    public final int type;
    public final String tag;
    public final String msg;
    public final Throwable throwable;

    public LogItem(int type, String tag, String msg, Throwable throwable) {
      this.timeString = null;
      this.type = type;
      this.tag = tag;
      this.msg = msg;
      this.throwable = throwable;
    }

    public LogItem(String timeString, int type, String tag, String msg, Throwable throwable) {
      this.timeString = timeString;
      this.type = type;
      this.tag = tag;
      this.msg = msg;
      this.throwable = throwable;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (!(o instanceof LogItem)) {
        return false;
      }

      LogItem log = (LogItem) o;
      return type == log.type
          && !(timeString != null ? !timeString.equals(log.timeString) : log.timeString != null)
          && !(msg != null ? !msg.equals(log.msg) : log.msg != null)
          && !(tag != null ? !tag.equals(log.tag) : log.tag != null)
          && !(throwable != null ? !throwable.equals(log.throwable) : log.throwable != null);
    }

    @Override
    public int hashCode() {
      int result = 0;
      result = 31 * result + (timeString != null ? timeString.hashCode() : 0);
      result = 31 * result + type;
      result = 31 * result + (tag != null ? tag.hashCode() : 0);
      result = 31 * result + (msg != null ? msg.hashCode() : 0);
      result = 31 * result + (throwable != null ? throwable.hashCode() : 0);
      return result;
    }

    @Override
    public String toString() {
      return "LogItem{"
          + "\n  timeString='"
          + timeString
          + '\''
          + "\n  type="
          + type
          + "\n  tag='"
          + tag
          + '\''
          + "\n  msg='"
          + msg
          + '\''
          + "\n  throwable="
          + (throwable == null ? null : Throwables.getStackTraceAsString(throwable))
          + "\n}";
    }
  }

  /**
   * Failure thrown when wtf_is_fatal is true and Log.wtf is called. This is a parallel
   * implementation of framework's hidden API {@link android.util.Log#TerribleFailure}, to allow
   * tests to catch / expect these exceptions.
   */
  public static final class TerribleFailure extends RuntimeException {
    TerribleFailure(String msg, Throwable cause) {
      super(msg, cause);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy