Home | History | Annotate | Download | only in logging
      1 package com.android.launcher3.logging;
      2 
      3 import android.os.Handler;
      4 import android.os.HandlerThread;
      5 import android.os.Message;
      6 import android.util.Log;
      7 import android.util.Pair;
      8 
      9 import com.android.launcher3.LauncherModel;
     10 import com.android.launcher3.Utilities;
     11 import com.android.launcher3.config.ProviderConfig;
     12 
     13 import java.io.BufferedReader;
     14 import java.io.File;
     15 import java.io.FileReader;
     16 import java.io.FileWriter;
     17 import java.io.PrintWriter;
     18 import java.text.DateFormat;
     19 import java.util.Calendar;
     20 import java.util.Date;
     21 import java.util.concurrent.CountDownLatch;
     22 import java.util.concurrent.TimeUnit;
     23 
     24 /**
     25  * Wrapper around {@link Log} to allow writing to a file.
     26  * This class can safely be called from main thread.
     27  *
     28  * Note: This should only be used for logging errors which have a persistent effect on user's data,
     29  * but whose effect may not be visible immediately.
     30  */
     31 public final class FileLog {
     32 
     33     private static final String FILE_NAME_PREFIX = "log-";
     34     private static final DateFormat DATE_FORMAT =
     35             DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
     36 
     37     private static final long MAX_LOG_FILE_SIZE = 4 << 20;  // 4 mb
     38 
     39     private static Handler sHandler = null;
     40     private static File sLogsDirectory = null;
     41 
     42     public static void setDir(File logsDir) {
     43         if (ProviderConfig.IS_DOGFOOD_BUILD) {
     44             synchronized (DATE_FORMAT) {
     45                 // If the target directory changes, stop any active thread.
     46                 if (sHandler != null && !logsDir.equals(sLogsDirectory)) {
     47                     ((HandlerThread) sHandler.getLooper().getThread()).quit();
     48                     sHandler = null;
     49                 }
     50             }
     51         }
     52         sLogsDirectory = logsDir;
     53     }
     54 
     55     public static void d(String tag, String msg, Exception e) {
     56         Log.d(tag, msg, e);
     57         print(tag, msg, e);
     58     }
     59 
     60     public static void d(String tag, String msg) {
     61         Log.d(tag, msg);
     62         print(tag, msg);
     63     }
     64 
     65     public static void e(String tag, String msg, Exception e) {
     66         Log.e(tag, msg, e);
     67         print(tag, msg, e);
     68     }
     69 
     70     public static void e(String tag, String msg) {
     71         Log.e(tag, msg);
     72         print(tag, msg);
     73     }
     74 
     75     public static void print(String tag, String msg) {
     76         print(tag, msg, null);
     77     }
     78 
     79     public static void print(String tag, String msg, Exception e) {
     80         if (!ProviderConfig.IS_DOGFOOD_BUILD) {
     81             return;
     82         }
     83         String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
     84         if (e != null) {
     85             out += "\n" + Log.getStackTraceString(e);
     86         }
     87         Message.obtain(getHandler(), LogWriterCallback.MSG_WRITE, out).sendToTarget();
     88     }
     89 
     90     private static Handler getHandler() {
     91         synchronized (DATE_FORMAT) {
     92             if (sHandler == null) {
     93                 HandlerThread thread = new HandlerThread("file-logger");
     94                 thread.start();
     95                 sHandler = new Handler(thread.getLooper(), new LogWriterCallback());
     96             }
     97         }
     98         return sHandler;
     99     }
    100 
    101     /**
    102      * Blocks until all the pending logs are written to the disk
    103      * @param out if not null, all the persisted logs are copied to the writer.
    104      */
    105     public static void flushAll(PrintWriter out) throws InterruptedException {
    106         if (!ProviderConfig.IS_DOGFOOD_BUILD) {
    107             return;
    108         }
    109         CountDownLatch latch = new CountDownLatch(1);
    110         Message.obtain(getHandler(), LogWriterCallback.MSG_FLUSH,
    111                 Pair.create(out, latch)).sendToTarget();
    112 
    113         latch.await(2, TimeUnit.SECONDS);
    114     }
    115 
    116     /**
    117      * Writes logs to the file.
    118      * Log files are named log-0 for even days of the year and log-1 for odd days of the year.
    119      * Logs older than 36 hours are purged.
    120      */
    121     private static class LogWriterCallback implements Handler.Callback {
    122 
    123         private static final long CLOSE_DELAY = 5000;  // 5 seconds
    124 
    125         private static final int MSG_WRITE = 1;
    126         private static final int MSG_CLOSE = 2;
    127         private static final int MSG_FLUSH = 3;
    128 
    129         private String mCurrentFileName = null;
    130         private PrintWriter mCurrentWriter = null;
    131 
    132         private void closeWriter() {
    133             Utilities.closeSilently(mCurrentWriter);
    134             mCurrentWriter = null;
    135         }
    136 
    137         @Override
    138         public boolean handleMessage(Message msg) {
    139             if (sLogsDirectory == null || !ProviderConfig.IS_DOGFOOD_BUILD) {
    140                 return true;
    141             }
    142             switch (msg.what) {
    143                 case MSG_WRITE: {
    144                     Calendar cal = Calendar.getInstance();
    145                     // suffix with 0 or 1 based on the day of the year.
    146                     String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) & 1);
    147 
    148                     if (!fileName.equals(mCurrentFileName)) {
    149                         closeWriter();
    150                     }
    151 
    152                     try {
    153                         if (mCurrentWriter == null) {
    154                             mCurrentFileName = fileName;
    155 
    156                             boolean append = false;
    157                             File logFile = new File(sLogsDirectory, fileName);
    158                             if (logFile.exists()) {
    159                                 Calendar modifiedTime = Calendar.getInstance();
    160                                 modifiedTime.setTimeInMillis(logFile.lastModified());
    161 
    162                                 // If the file was modified more that 36 hours ago, purge the file.
    163                                 // We use instead of 24 to account for day-365 followed by day-1
    164                                 modifiedTime.add(Calendar.HOUR, 36);
    165                                 append = cal.before(modifiedTime)
    166                                         && logFile.length() < MAX_LOG_FILE_SIZE;
    167                             }
    168                             mCurrentWriter = new PrintWriter(new FileWriter(logFile, append));
    169                         }
    170 
    171                         mCurrentWriter.println((String) msg.obj);
    172                         mCurrentWriter.flush();
    173 
    174                         // Auto close file stream after some time.
    175                         sHandler.removeMessages(MSG_CLOSE);
    176                         sHandler.sendEmptyMessageDelayed(MSG_CLOSE, CLOSE_DELAY);
    177                     } catch (Exception e) {
    178                         Log.e("FileLog", "Error writing logs to file", e);
    179                         // Close stream, will try reopening during next log
    180                         closeWriter();
    181                     }
    182                     return true;
    183                 }
    184                 case MSG_CLOSE: {
    185                     closeWriter();
    186                     return true;
    187                 }
    188                 case MSG_FLUSH: {
    189                     closeWriter();
    190                     Pair<PrintWriter, CountDownLatch> p =
    191                             (Pair<PrintWriter, CountDownLatch>) msg.obj;
    192 
    193                     if (p.first != null) {
    194                         dumpFile(p.first, FILE_NAME_PREFIX + 0);
    195                         dumpFile(p.first, FILE_NAME_PREFIX + 1);
    196                     }
    197                     p.second.countDown();
    198                     return true;
    199                 }
    200             }
    201             return true;
    202         }
    203     }
    204 
    205     private static void dumpFile(PrintWriter out, String fileName) {
    206         File logFile = new File(sLogsDirectory, fileName);
    207         if (logFile.exists()) {
    208 
    209             BufferedReader in = null;
    210             try {
    211                 in = new BufferedReader(new FileReader(logFile));
    212                 out.println();
    213                 out.println("--- logfile: " + fileName + " ---");
    214                 String line;
    215                 while ((line = in.readLine()) != null) {
    216                     out.println(line);
    217                 }
    218             } catch (Exception e) {
    219                 // ignore
    220             } finally {
    221                 Utilities.closeSilently(in);
    222             }
    223         }
    224     }
    225 }
    226