1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.log;
18 
19 import com.android.ddmlib.Log;
20 import com.android.ddmlib.Log.LogLevel;
21 
22 import java.io.PrintWriter;
23 import java.io.StringWriter;
24 import java.text.SimpleDateFormat;
25 import java.util.Date;
26 
27 /**
28  * A logging utility class.  Useful for code that needs to override static methods from {@link Log}
29  */
30 public class LogUtil {
31 
32     /**
33      * Make uninstantiable
34      */
LogUtil()35     private LogUtil() {}
36 
37     /**
38      * Sent when a log message needs to be printed.  This implementation prints the message to
39      * stdout in all cases.
40      *
41      * @param logLevel The {@link LogLevel} enum representing the priority of the message.
42      * @param tag The tag associated with the message.
43      * @param message The message to display.
44      */
printLog(LogLevel logLevel, String tag, String message)45     public static void printLog(LogLevel logLevel, String tag, String message) {
46         System.out.print(LogUtil.getLogFormatString(logLevel, tag, message));
47     }
48 
49     /**
50      * Creates a format string that is similar to the "threadtime" log format on the device.  This
51      * is specifically useful because it includes the day and month (to differentiate times for
52      * long-running TF instances), and also uses 24-hour time to disambiguate morning from evening.
53      * <p/>
54      * @see Log#getLogFormatString(LogLevel, String, String)
55      */
getLogFormatString(LogLevel logLevel, String tag, String message)56     public static String getLogFormatString(LogLevel logLevel, String tag, String message) {
57         SimpleDateFormat formatter = new SimpleDateFormat("MM-dd HH:mm:ss");
58         return String.format("%s %c/%s: %s\n", formatter.format(new Date()),
59                 logLevel.getPriorityLetter(), tag, message);
60     }
61 
62     /**
63      * A shim class for {@link Log} that automatically uses the simple classname of the caller as
64      * the log tag
65      */
66     public static class CLog {
67 
68         protected static final String CLASS_NAME = CLog.class.getName();
69 
70         /**
71          * The shim version of {@link Log#v(String, String)}.
72          *
73          * @param message The {@code String} to log
74          */
v(String message)75         public static void v(String message) {
76             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
77             Log.v(getClassName(2), message);
78         }
79 
80         /**
81          * The shim version of {@link Log#v(String, String)}.  Also calls String.format for
82          * convenience.
83          *
84          * @param format A format string for the message to log
85          * @param args The format string arguments
86          */
v(String format, Object... args)87         public static void v(String format, Object... args) {
88             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
89             Log.v(getClassName(2), String.format(format, args));
90         }
91 
92         /**
93          * The shim version of {@link Log#d(String, String)}.
94          *
95          * @param message The {@code String} to log
96          */
d(String message)97         public static void d(String message) {
98             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
99             Log.d(getClassName(2), message);
100         }
101 
102         /**
103          * The shim version of {@link Log#d(String, String)}.  Also calls String.format for
104          * convenience.
105          *
106          * @param format A format string for the message to log
107          * @param args The format string arguments
108          */
d(String format, Object... args)109         public static void d(String format, Object... args) {
110             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
111             Log.d(getClassName(2), String.format(format, args));
112         }
113 
114         /**
115          * The shim version of {@link Log#i(String, String)}.
116          *
117          * @param message The {@code String} to log
118          */
i(String message)119         public static void i(String message) {
120             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
121             Log.i(getClassName(2), message);
122         }
123 
124         /**
125          * The shim version of {@link Log#i(String, String)}.  Also calls String.format for
126          * convenience.
127          *
128          * @param format A format string for the message to log
129          * @param args The format string arguments
130          */
i(String format, Object... args)131         public static void i(String format, Object... args) {
132             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
133             Log.i(getClassName(2), String.format(format, args));
134         }
135 
136         /**
137          * The shim version of {@link Log#w(String, String)}.
138          *
139          * @param message The {@code String} to log
140          */
w(String message)141         public static void w(String message) {
142             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
143             Log.w(getClassName(2), message);
144         }
145 
146         /**
147          * A variation of {@link Log#w(String, String)}, where the stack trace of provided
148          * {@link Throwable} is formatted and logged.
149          *
150          * @param t The {@link Throwable} to log
151          */
w(Throwable t)152         public static void w(Throwable t) {
153             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
154             Log.w(getClassName(2), getStackTraceString(t));
155         }
156 
157         /**
158          * The shim version of {@link Log#w(String, String)}.  Also calls String.format for
159          * convenience.
160          *
161          * @param format A format string for the message to log
162          * @param args The format string arguments
163          */
w(String format, Object... args)164         public static void w(String format, Object... args) {
165             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
166             Log.w(getClassName(2), String.format(format, args));
167         }
168 
169         /**
170          * The shim version of {@link Log#e(String, String)}.
171          *
172          * @param message The {@code String} to log
173          */
e(String message)174         public static void e(String message) {
175             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
176             Log.e(getClassName(2), message);
177         }
178 
179         /**
180          * The shim version of {@link Log#e(String, String)}.  Also calls String.format for
181          * convenience.
182          *
183          * @param format A format string for the message to log
184          * @param args The format string arguments
185          */
e(String format, Object... args)186         public static void e(String format, Object... args) {
187             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
188             Log.e(getClassName(2), String.format(format, args));
189         }
190 
191         /**
192          * The shim version of {@link Log#e(String, Throwable)}.
193          *
194          * @param t the {@link Throwable} to output.
195          */
e(Throwable t)196         public static void e(Throwable t) {
197             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
198             Log.e(getClassName(2), t);
199         }
200 
201         /**
202          * The shim version of {@link Log#logAndDisplay(LogLevel, String, String)}.
203          *
204          * @param logLevel the {@link LogLevel}
205          * @param message The {@code String} to log
206          */
logAndDisplay(LogLevel logLevel, String message)207         public static void logAndDisplay(LogLevel logLevel, String message) {
208             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
209             Log.logAndDisplay(logLevel, getClassName(2), message);
210         }
211 
212         /**
213          * The shim version of {@link Log#logAndDisplay(LogLevel, String, String)}.
214          *
215          * @param logLevel the {@link LogLevel}
216          * @param format A format string for the message to log
217          * @param args The format string arguments
218          */
logAndDisplay(LogLevel logLevel, String format, Object... args)219         public static void logAndDisplay(LogLevel logLevel, String format, Object... args) {
220             // frame 2: skip frames 0 (#getClassName) and 1 (this method)
221             Log.logAndDisplay(logLevel, getClassName(2), String.format(format, args));
222         }
223 
224         /**
225          * What a Terrible Failure: Report a condition that should never happen.
226          * The error will always be logged at level ASSERT with the call stack.
227          *
228          * @param message The message you would like logged.
229          */
wtf(String message)230         public static void wtf(String message) {
231             wtf(message, (Throwable) null);
232         }
233 
234         /**
235          * What a Terrible Failure: Report a condition that should never happen.
236          * The error will always be logged at level ASSERT with the call stack.
237          *
238          * @param t (Optional) An exception to log. If null, only message will be logged.
239          */
wtf(Throwable t)240         public static void wtf(Throwable t) {
241             wtf(t.getMessage(), t);
242         }
243 
244         /**
245          * What a Terrible Failure: Report a condition that should never happen.
246          * The error will always be logged at level ASSERT with the call stack.
247          * Also calls String.format for convenience.
248          *
249          * @param format A format string for the message to log
250          * @param args The format string arguments
251          */
wtf(String format, Object... args)252         public static void wtf(String format, Object... args) {
253             wtf(String.format(format, args), (Throwable) null);
254         }
255 
256         /**
257          * What a Terrible Failure: Report a condition that should never happen.
258          * The error will always be logged at level ASSERT with the call stack.
259          *
260          * @param message The message you would like logged.
261          * @param t (Optional) An exception to log. If null, only message will be logged.
262          */
wtf(String message, Throwable t)263         public static void wtf(String message, Throwable t) {
264             String tag = findCallerClassName();
265             String logMessage = "WTF - " + message;
266             String stackTrace = getStackTraceString(t);
267             if (stackTrace.length() > 0) {
268                logMessage += "\n" + stackTrace;
269             }
270 
271             Log.logAndDisplay(LogLevel.ASSERT, tag, logMessage);
272         }
273 
274         /**
275          * A helper method that parses the stack trace string out of the
276          * throwable.
277          *
278          * @param t contains the stack trace information
279          * @return A {@link String} containing the stack trace of the throwable.
280          */
getStackTraceString(Throwable t)281         private static String getStackTraceString(Throwable t) {
282             if (t == null) {
283                 return "";
284             }
285 
286             StringWriter sw = new StringWriter();
287             PrintWriter pw = new PrintWriter(sw);
288             t.printStackTrace(pw);
289             pw.flush();
290             return sw.toString();
291         }
292 
293         /**
294          * Return the simple classname from the {@code frame}th stack frame in the call path.
295          * Note: this method does <emph>not</emph> check array bounds for the stack trace length.
296          *
297          * @param frame The index of the stack trace frame to inspect for the class name
298          * @return The simple class name (or full-qualified if an error occurs getting a ref to the
299          *         class) for the given element of the stack trace.
300          */
getClassName(int frame)301         public static String getClassName(int frame) {
302             StackTraceElement[] frames = new Throwable().getStackTrace();
303             return parseClassName(frames[frame].getClassName());
304         }
305 
306         /**
307          * Finds the external class name that directly called a CLog method.
308          *
309          * @return The simple class name (or full-qualified if an error occurs getting a ref to
310          *         the class) of the external class that called a CLog method, or "Unknown" if
311          *         the stack trace is empty or only contains CLog class names.
312          */
findCallerClassName()313         public static String findCallerClassName() {
314             return findCallerClassName(null);
315         }
316 
317         /**
318          * Finds the external class name that directly called a CLog method.
319          *
320          * @param t (Optional) the stack trace to search within, exposed for unit testing
321          * @return The simple class name (or full-qualified if an error occurs getting a ref to
322          *         the class) of the external class that called a CLog method, or "Unknown" if
323          *         the stack trace is empty or only contains CLog class names.
324          */
findCallerClassName(Throwable t)325         public static String findCallerClassName(Throwable t) {
326             String className = "Unknown";
327 
328             if (t == null) {
329                 t = new Throwable();
330             }
331             StackTraceElement[] frames = t.getStackTrace();
332             if (frames.length == 0) {
333                 return className;
334             }
335 
336             // starting with the first frame's class name (this CLog class)
337             // keep iterating until a frame of a different class name is found
338             int f;
339             for (f = 0; f < frames.length; f++) {
340                 className = frames[f].getClassName();
341                 if (!className.equals(CLASS_NAME)) {
342                     break;
343                 }
344             }
345 
346             return parseClassName(className);
347         }
348 
349         /**
350          * Parses the simple class name out of the full class name. If the formatting already looks
351          * like a simple class name, then just returns that.
352          *
353          * @param fullName the full class name to parse
354          * @return The simple class name
355          */
parseClassName(String fullName)356         public static String parseClassName(String fullName) {
357             int lastdot = fullName.lastIndexOf('.');
358             String simpleName = fullName;
359             if (lastdot != -1) {
360                 simpleName = fullName.substring(lastdot + 1);
361             }
362             // handle inner class names
363             int lastdollar = simpleName.lastIndexOf('$');
364             if (lastdollar != -1) {
365                 simpleName = simpleName.substring(0, lastdollar);
366             }
367             return simpleName;
368         }
369     }
370 }
371