Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import android.util.Log;
      4 import java.io.FileOutputStream;
      5 import java.io.IOException;
      6 import java.io.PrintStream;
      7 import java.util.ArrayList;
      8 import java.util.Collections;
      9 import java.util.HashMap;
     10 import java.util.List;
     11 import java.util.Map;
     12 import java.util.Queue;
     13 import java.util.concurrent.ConcurrentLinkedQueue;
     14 import org.robolectric.annotation.Implementation;
     15 import org.robolectric.annotation.Implements;
     16 import org.robolectric.annotation.Resetter;
     17 
     18 @Implements(Log.class)
     19 public class ShadowLog {
     20   private static final int extraLogLength = "l/: \n".length();
     21   private static final Map<String, Queue<LogItem>> logsByTag = Collections.synchronizedMap(new
     22       HashMap<String, Queue<LogItem>>());
     23   private static final Queue<LogItem> logs = new ConcurrentLinkedQueue<>();
     24   public static PrintStream stream;
     25   private static final Map<String, Integer> tagToLevel = Collections.synchronizedMap(new
     26       HashMap<String, Integer>());
     27 
     28   @Implementation
     29   public static void e(String tag, String msg) {
     30     e(tag, msg, null);
     31   }
     32 
     33   @Implementation
     34   public static void e(String tag, String msg, Throwable throwable) {
     35     addLog(Log.ERROR, tag, msg, throwable);
     36   }
     37 
     38   @Implementation
     39   public static void d(String tag, String msg) {
     40     d(tag, msg, null);
     41   }
     42 
     43   @Implementation
     44   public static void d(String tag, String msg, Throwable throwable) {
     45     addLog(Log.DEBUG, tag, msg, throwable);
     46   }
     47 
     48   @Implementation
     49   public static void i(String tag, String msg) {
     50     i(tag, msg, null);
     51   }
     52 
     53   @Implementation
     54   public static void i(String tag, String msg, Throwable throwable) {
     55     addLog(Log.INFO, tag, msg, throwable);
     56   }
     57 
     58   @Implementation
     59   public static void v(String tag, String msg) {
     60     v(tag, msg, null);
     61   }
     62 
     63   @Implementation
     64   public static void v(String tag, String msg, Throwable throwable) {
     65     addLog(Log.VERBOSE, tag, msg, throwable);
     66   }
     67 
     68   @Implementation
     69   public static void w(String tag, String msg) {
     70     w(tag, msg, null);
     71   }
     72 
     73   @Implementation
     74   public static void w(String tag, Throwable throwable) {
     75     w(tag, null, throwable);
     76   }
     77 
     78 
     79   @Implementation
     80   public static void w(String tag, String msg, Throwable throwable) {
     81     addLog(Log.WARN, tag, msg, throwable);
     82   }
     83 
     84   @Implementation
     85   public static void wtf(String tag, String msg) {
     86     wtf(tag, msg, null);
     87   }
     88 
     89   @Implementation
     90   public static void wtf(String tag, String msg, Throwable throwable) {
     91     addLog(Log.ASSERT, tag, msg, throwable);
     92   }
     93 
     94   @Implementation
     95   public static boolean isLoggable(String tag, int level) {
     96     synchronized (tagToLevel) {
     97       if (tagToLevel.containsKey(tag)) {
     98         return level >= tagToLevel.get(tag);
     99       }
    100     }
    101     return stream != null || level >= Log.INFO;
    102   }
    103 
    104   @Implementation
    105   public static int println(int priority, String tag, String msg) {
    106     addLog(priority, tag, msg, null);
    107     int tagLength = tag == null ? 0 : tag.length();
    108     int msgLength = msg == null ? 0 : msg.length();
    109     return extraLogLength + tagLength + msgLength;
    110   }
    111 
    112   /**
    113    * Sets the log level of a given tag, that {@link #isLoggable} will follow.
    114    * @param tag A log tag
    115    * @param level A log level, from {@link android.util.Log}
    116    */
    117   public static void setLoggable(String tag, int level) {
    118     tagToLevel.put(tag, level);
    119   }
    120 
    121   private static void addLog(int level, String tag, String msg, Throwable throwable) {
    122     if (stream != null) {
    123       logToStream(stream, level, tag, msg, throwable);
    124     }
    125 
    126     LogItem item = new LogItem(level, tag, msg, throwable);
    127     Queue<LogItem> itemList;
    128 
    129     synchronized (logsByTag) {
    130       if (!logsByTag.containsKey(tag)) {
    131         itemList = new ConcurrentLinkedQueue<>();
    132         logsByTag.put(tag, itemList);
    133       } else {
    134         itemList = logsByTag.get(tag);
    135       }
    136     }
    137 
    138     itemList.add(item);
    139     logs.add(item);
    140   }
    141 
    142   private static void logToStream(PrintStream ps, int level, String tag, String msg, Throwable throwable) {
    143     final char c;
    144     switch (level) {
    145       case Log.ASSERT: c = 'A'; break;
    146       case Log.DEBUG:  c = 'D'; break;
    147       case Log.ERROR:  c = 'E'; break;
    148       case Log.WARN:   c = 'W'; break;
    149       case Log.INFO:   c = 'I'; break;
    150       case Log.VERBOSE:c = 'V'; break;
    151       default:         c = '?';
    152     }
    153     ps.println(c + "/" + tag + ": " + msg);
    154     if (throwable != null) {
    155       throwable.printStackTrace(ps);
    156     }
    157   }
    158 
    159   /**
    160    * Returns ordered list of all log entries.
    161    * @return List of log items
    162    */
    163   public static List<LogItem> getLogs() {
    164     return new ArrayList<>(logs);
    165   }
    166 
    167   /**
    168    * Returns ordered list of all log items for a specific tag.
    169    *
    170    * @param tag The tag to get logs for
    171    * @return The list of log items for the tag or an empty list if no logs for that tag exist.
    172    */
    173   public static List<LogItem> getLogsForTag(String tag) {
    174     Queue<LogItem> logs = logsByTag.get(tag);
    175     return logs == null ? Collections.emptyList() : new ArrayList<>(logs);
    176   }
    177 
    178   /**
    179    * Clear all accummulated logs.
    180    */
    181   public static void clear() {
    182     reset();
    183   }
    184 
    185   @Resetter
    186   public static void reset() {
    187     logs.clear();
    188     logsByTag.clear();
    189     tagToLevel.clear();
    190   }
    191 
    192   public static void setupLogging() {
    193     String logging = System.getProperty("robolectric.logging");
    194     if (logging != null && stream == null) {
    195       PrintStream stream = null;
    196       if ("stdout".equalsIgnoreCase(logging)) {
    197         stream = System.out;
    198       } else if ("stderr".equalsIgnoreCase(logging)) {
    199         stream = System.err;
    200       } else {
    201         try {
    202           final PrintStream file = new PrintStream(new FileOutputStream(logging), true);
    203           stream = file;
    204           Runtime.getRuntime().addShutdownHook(new Thread() {
    205             @Override public void run() {
    206               try {
    207                 file.close();
    208               } catch (Exception ignored) {
    209               }
    210             }
    211           });
    212         } catch (IOException e) {
    213           e.printStackTrace();
    214         }
    215       }
    216       ShadowLog.stream = stream;
    217     }
    218   }
    219 
    220   public static class LogItem {
    221     public final int type;
    222     public final String tag;
    223     public final String msg;
    224     public final Throwable throwable;
    225 
    226     public LogItem(int type, String tag, String msg, Throwable throwable) {
    227       this.type = type;
    228       this.tag = tag;
    229       this.msg = msg;
    230       this.throwable = throwable;
    231     }
    232 
    233     @Override
    234     public boolean equals(Object o) {
    235       if (this == o) return true;
    236       if (o == null || getClass() != o.getClass()) return false;
    237 
    238       LogItem log = (LogItem) o;
    239       return type == log.type
    240           && !(msg != null ? !msg.equals(log.msg) : log.msg != null)
    241           && !(tag != null ? !tag.equals(log.tag) : log.tag != null)
    242           && !(throwable != null ? !throwable.equals(log.throwable) : log.throwable != null);
    243     }
    244 
    245     @Override
    246     public int hashCode() {
    247       int result = type;
    248       result = 31 * result + (tag != null ? tag.hashCode() : 0);
    249       result = 31 * result + (msg != null ? msg.hashCode() : 0);
    250       result = 31 * result + (throwable != null ? throwable.hashCode() : 0);
    251       return result;
    252     }
    253 
    254     @Override
    255     public String toString() {
    256       return "LogItem{" +
    257           "type=" + type +
    258           ", tag='" + tag + '\'' +
    259           ", msg='" + msg + '\'' +
    260           ", throwable=" + throwable +
    261           '}';
    262     }
    263   }
    264 }
    265