Home | History | Annotate | Download | only in log
      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      */
     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      */
     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      */
     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          */
     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          */
     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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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
    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          */
    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          */
    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          */
    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          */
    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          */
    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
    384          * looks 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          */
    389         // @VisibleForTesting
    390         static String parseClassName(String fullName) {
    391             int lastdot = fullName.lastIndexOf('.');
    392             String simpleName = fullName;
    393             if (lastdot != -1) {
    394                 simpleName = fullName.substring(lastdot + 1);
    395             }
    396             // handle inner class names
    397             int lastdollar = simpleName.lastIndexOf('$');
    398             if (lastdollar != -1) {
    399                 simpleName = simpleName.substring(0, lastdollar);
    400             }
    401             return simpleName;
    402         }
    403     }
    404 }
    405