• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   /**
29    * Whether calling {@link Log#wtf} will throw {@link TerribleFailure}. This is analogous to
30    * Android's {@link android.provider.Settings.Global#WTF_IS_FATAL}. The default value is false to
31    * preserve existing behavior.
32    */
33   private static boolean wtfIsFatal = false;
34 
35   @Implementation
e(String tag, String msg)36   protected static int e(String tag, String msg) {
37     return e(tag, msg, null);
38   }
39 
40   @Implementation
e(String tag, String msg, Throwable throwable)41   protected static int e(String tag, String msg, Throwable throwable) {
42     return addLog(Log.ERROR, tag, msg, throwable);
43   }
44 
45   @Implementation
d(String tag, String msg)46   protected static int d(String tag, String msg) {
47     return d(tag, msg, null);
48   }
49 
50   @Implementation
d(String tag, String msg, Throwable throwable)51   protected static int d(String tag, String msg, Throwable throwable) {
52     return addLog(Log.DEBUG, tag, msg, throwable);
53   }
54 
55   @Implementation
i(String tag, String msg)56   protected static int i(String tag, String msg) {
57     return i(tag, msg, null);
58   }
59 
60   @Implementation
i(String tag, String msg, Throwable throwable)61   protected static int i(String tag, String msg, Throwable throwable) {
62     return addLog(Log.INFO, tag, msg, throwable);
63   }
64 
65   @Implementation
v(String tag, String msg)66   protected static int v(String tag, String msg) {
67     return v(tag, msg, null);
68   }
69 
70   @Implementation
v(String tag, String msg, Throwable throwable)71   protected static int v(String tag, String msg, Throwable throwable) {
72     return addLog(Log.VERBOSE, tag, msg, throwable);
73   }
74 
75   @Implementation
w(String tag, String msg)76   protected static int w(String tag, String msg) {
77     return w(tag, msg, null);
78   }
79 
80   @Implementation
w(String tag, Throwable throwable)81   protected static int w(String tag, Throwable throwable) {
82     return w(tag, null, throwable);
83   }
84 
85   @Implementation
w(String tag, String msg, Throwable throwable)86   protected static int w(String tag, String msg, Throwable throwable) {
87     return addLog(Log.WARN, tag, msg, throwable);
88   }
89 
90   @Implementation
wtf(String tag, String msg)91   protected static int wtf(String tag, String msg) {
92     return wtf(tag, msg, null);
93   }
94 
95   @Implementation
wtf(String tag, String msg, Throwable throwable)96   protected static int wtf(String tag, String msg, Throwable throwable) {
97     addLog(Log.ASSERT, tag, msg, throwable);
98     if (wtfIsFatal) {
99       throw new TerribleFailure(msg, throwable);
100     }
101     return 0;
102   }
103 
104   /** Sets whether calling {@link Log#wtf} will throw {@link TerribleFailure}. */
setWtfIsFatal(boolean fatal)105   public static void setWtfIsFatal(boolean fatal) {
106     wtfIsFatal = fatal;
107   }
108 
109   @Implementation
isLoggable(String tag, int level)110   protected static boolean isLoggable(String tag, int level) {
111     synchronized (tagToLevel) {
112       if (tagToLevel.containsKey(tag)) {
113         return level >= tagToLevel.get(tag);
114       }
115     }
116     return stream != null || level >= Log.INFO;
117   }
118 
119   @Implementation
println_native(int bufID, int priority, String tag, String msg)120   protected static int println_native(int bufID, int priority, String tag, String msg) {
121     addLog(priority, tag, msg, null);
122     int tagLength = tag == null ? 0 : tag.length();
123     int msgLength = msg == null ? 0 : msg.length();
124     return extraLogLength + tagLength + msgLength;
125   }
126 
127   /**
128    * Sets the log level of a given tag, that {@link #isLoggable} will follow.
129    * @param tag A log tag
130    * @param level A log level, from {@link android.util.Log}
131    */
setLoggable(String tag, int level)132   public static void setLoggable(String tag, int level) {
133     tagToLevel.put(tag, level);
134   }
135 
addLog(int level, String tag, String msg, Throwable throwable)136   private static int addLog(int level, String tag, String msg, Throwable throwable) {
137     if (stream != null) {
138       logToStream(stream, level, tag, msg, throwable);
139     }
140 
141     LogItem item = new LogItem(level, tag, msg, throwable);
142     Queue<LogItem> itemList;
143 
144     synchronized (logsByTag) {
145       if (!logsByTag.containsKey(tag)) {
146         itemList = new ConcurrentLinkedQueue<>();
147         logsByTag.put(tag, itemList);
148       } else {
149         itemList = logsByTag.get(tag);
150       }
151     }
152 
153     itemList.add(item);
154     logs.add(item);
155 
156     return 0;
157   }
158 
logToStream(PrintStream ps, int level, String tag, String msg, Throwable throwable)159   private static void logToStream(PrintStream ps, int level, String tag, String msg, Throwable throwable) {
160     final char c;
161     switch (level) {
162       case Log.ASSERT: c = 'A'; break;
163       case Log.DEBUG:  c = 'D'; break;
164       case Log.ERROR:  c = 'E'; break;
165       case Log.WARN:   c = 'W'; break;
166       case Log.INFO:   c = 'I'; break;
167       case Log.VERBOSE:c = 'V'; break;
168       default:         c = '?';
169     }
170     ps.println(c + "/" + tag + ": " + msg);
171     if (throwable != null) {
172       throwable.printStackTrace(ps);
173     }
174   }
175 
176   /**
177    * Returns ordered list of all log entries.
178    * @return List of log items
179    */
getLogs()180   public static List<LogItem> getLogs() {
181     return new ArrayList<>(logs);
182   }
183 
184   /**
185    * Returns ordered list of all log items for a specific tag.
186    *
187    * @param tag The tag to get logs for
188    * @return The list of log items for the tag or an empty list if no logs for that tag exist.
189    */
getLogsForTag(String tag)190   public static List<LogItem> getLogsForTag(String tag) {
191     Queue<LogItem> logs = logsByTag.get(tag);
192     return logs == null ? Collections.emptyList() : new ArrayList<>(logs);
193   }
194 
195   /** Clear all accumulated logs. */
clear()196   public static void clear() {
197     reset();
198   }
199 
200   @Resetter
reset()201   public static void reset() {
202     logs.clear();
203     logsByTag.clear();
204     tagToLevel.clear();
205     wtfIsFatal = false;
206   }
207 
208   @SuppressWarnings("CatchAndPrintStackTrace")
setupLogging()209   public static void setupLogging() {
210     String logging = System.getProperty("robolectric.logging");
211     if (logging != null && stream == null) {
212       PrintStream stream = null;
213       if ("stdout".equalsIgnoreCase(logging)) {
214         stream = System.out;
215       } else if ("stderr".equalsIgnoreCase(logging)) {
216         stream = System.err;
217       } else {
218         try {
219           final PrintStream file = new PrintStream(new FileOutputStream(logging), true);
220           stream = file;
221           Runtime.getRuntime().addShutdownHook(new Thread() {
222             @Override public void run() {
223               try {
224                 file.close();
225               } catch (Exception ignored) {
226               }
227             }
228           });
229         } catch (IOException e) {
230           e.printStackTrace();
231         }
232       }
233       ShadowLog.stream = stream;
234     }
235   }
236 
237   public static class LogItem {
238     public final int type;
239     public final String tag;
240     public final String msg;
241     public final Throwable throwable;
242 
LogItem(int type, String tag, String msg, Throwable throwable)243     public LogItem(int type, String tag, String msg, Throwable throwable) {
244       this.type = type;
245       this.tag = tag;
246       this.msg = msg;
247       this.throwable = throwable;
248     }
249 
250     @Override
equals(Object o)251     public boolean equals(Object o) {
252       if (this == o) return true;
253       if (o == null || getClass() != o.getClass()) return false;
254 
255       LogItem log = (LogItem) o;
256       return type == log.type
257           && !(msg != null ? !msg.equals(log.msg) : log.msg != null)
258           && !(tag != null ? !tag.equals(log.tag) : log.tag != null)
259           && !(throwable != null ? !throwable.equals(log.throwable) : log.throwable != null);
260     }
261 
262     @Override
hashCode()263     public int hashCode() {
264       int result = type;
265       result = 31 * result + (tag != null ? tag.hashCode() : 0);
266       result = 31 * result + (msg != null ? msg.hashCode() : 0);
267       result = 31 * result + (throwable != null ? throwable.hashCode() : 0);
268       return result;
269     }
270 
271     @Override
toString()272     public String toString() {
273       return "LogItem{" +
274           "type=" + type +
275           ", tag='" + tag + '\'' +
276           ", msg='" + msg + '\'' +
277           ", throwable=" + throwable +
278           '}';
279     }
280   }
281 
282   /**
283    * Failure thrown when wtf_is_fatal is true and Log.wtf is called. This is a parallel
284    * implementation of framework's hidden API {@link android.util.Log#TerribleFailure}, to allow
285    * tests to catch / expect these exceptions.
286    */
287   public static class TerribleFailure extends RuntimeException {
TerribleFailure(String msg, Throwable cause)288     public TerribleFailure(String msg, Throwable cause) {
289       super(msg, cause);
290     }
291   }
292 }
293