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 java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;

@Implements(Log.class)
public class ShadowLog {
  private static final int extraLogLength = "l/: \n".length();
  private static final Map> logsByTag = Collections.synchronizedMap(new
      HashMap>());
  private static final Queue logs = new ConcurrentLinkedQueue<>();
  public static PrintStream stream;
  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;

  @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;
  }

  @Implementation
  protected static boolean isLoggable(String tag, int level) {
    synchronized (tagToLevel) {
      if (tagToLevel.containsKey(tag)) {
        return level >= tagToLevel.get(tag);
      }
    }
    return stream != null || 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 extraLogLength + 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) {
    if (stream != null) {
      logToStream(stream, level, tag, msg, throwable);
    }

    LogItem item = new LogItem(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;
  }

  private static void logToStream(PrintStream ps, int level, String tag, String msg, Throwable throwable) {
    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 = '?';
    }
    ps.println(c + "/" + tag + ": " + msg);
    if (throwable != null) {
      throwable.printStackTrace(ps);
    }
  }

  /**
   * Returns ordered list of all log entries.
   * @return List of log items
   */
  public static List getLogs() {
    return new ArrayList<>(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 List getLogsForTag(String tag) {
    Queue logs = logsByTag.get(tag);
    return logs == null ? Collections.emptyList() : new ArrayList<>(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 ("stdout".equalsIgnoreCase(logging)) {
        stream = System.out;
      } else if ("stderr".equalsIgnoreCase(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() {
              try {
                file.close();
              } catch (Exception ignored) {
              }
            }
          });
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      ShadowLog.stream = stream;
    }
  }

  public static class LogItem {
    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.type = type;
      this.tag = tag;
      this.msg = msg;
      this.throwable = throwable;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      LogItem log = (LogItem) o;
      return type == log.type
          && !(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 = 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{" +
          "type=" + type +
          ", tag='" + tag + '\'' +
          ", msg='" + msg + '\'' +
          ", throwable=" + throwable +
          '}';
    }
  }

  /**
   * 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 class TerribleFailure extends RuntimeException {
    public TerribleFailure(String msg, Throwable cause) {
      super(msg, cause);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy