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