1713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalpackage com.android.launcher3.logging; 2713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 3713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport android.os.Handler; 414c73cc1200ff509ff12ad04157f31348f4c6736Sunny Goyalimport android.os.HandlerThread; 5713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport android.os.Message; 6713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport android.util.Log; 7713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport android.util.Pair; 8713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 9713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport com.android.launcher3.LauncherModel; 10713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport com.android.launcher3.Utilities; 11713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport com.android.launcher3.config.ProviderConfig; 12713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 13713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.io.BufferedReader; 14713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.io.File; 15713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.io.FileReader; 16713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.io.FileWriter; 17713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.io.PrintWriter; 18713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.text.DateFormat; 19713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.util.Calendar; 20713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.util.Date; 21713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.util.concurrent.CountDownLatch; 22713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalimport java.util.concurrent.TimeUnit; 23713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 24713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal/** 25713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal * Wrapper around {@link Log} to allow writing to a file. 26713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal * This class can safely be called from main thread. 2714c73cc1200ff509ff12ad04157f31348f4c6736Sunny Goyal * 2814c73cc1200ff509ff12ad04157f31348f4c6736Sunny Goyal * Note: This should only be used for logging errors which have a persistent effect on user's data, 2914c73cc1200ff509ff12ad04157f31348f4c6736Sunny Goyal * but whose effect may not be visible immediately. 30713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal */ 31713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyalpublic final class FileLog { 32713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 33713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static final String FILE_NAME_PREFIX = "log-"; 34713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static final DateFormat DATE_FORMAT = 35713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); 36713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 37713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static final long MAX_LOG_FILE_SIZE = 4 << 20; // 4 mb 38713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 39713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static Handler sHandler = null; 40713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static File sLogsDirectory = null; 41713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 42713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void setDir(File logsDir) { 43e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal if (ProviderConfig.IS_DOGFOOD_BUILD) { 44e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal synchronized (DATE_FORMAT) { 45e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal // If the target directory changes, stop any active thread. 46e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal if (sHandler != null && !logsDir.equals(sLogsDirectory)) { 47e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal ((HandlerThread) sHandler.getLooper().getThread()).quit(); 48e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal sHandler = null; 49e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal } 50e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal } 51e0e0e1d0004ab443c97aa96e390581fbce85fcd1Sunny Goyal } 52713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal sLogsDirectory = logsDir; 53713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 54713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 55713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void d(String tag, String msg, Exception e) { 56713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Log.d(tag, msg, e); 57713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal print(tag, msg, e); 58713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 59713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 60713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void d(String tag, String msg) { 61713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Log.d(tag, msg); 62713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal print(tag, msg); 63713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 64713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 65713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void e(String tag, String msg, Exception e) { 66713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Log.e(tag, msg, e); 67713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal print(tag, msg, e); 68713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 69713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 70713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void e(String tag, String msg) { 71713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Log.e(tag, msg); 72713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal print(tag, msg); 73713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 74713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 75713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void print(String tag, String msg) { 76713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal print(tag, msg, null); 77713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 78713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 79713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void print(String tag, String msg, Exception e) { 80713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (!ProviderConfig.IS_DOGFOOD_BUILD) { 81713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return; 82713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 83713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg); 84713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (e != null) { 85713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal out += "\n" + Log.getStackTraceString(e); 86713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 87713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Message.obtain(getHandler(), LogWriterCallback.MSG_WRITE, out).sendToTarget(); 88713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 89713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 90713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static Handler getHandler() { 91713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal synchronized (DATE_FORMAT) { 92713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (sHandler == null) { 9314c73cc1200ff509ff12ad04157f31348f4c6736Sunny Goyal HandlerThread thread = new HandlerThread("file-logger"); 9414c73cc1200ff509ff12ad04157f31348f4c6736Sunny Goyal thread.start(); 9514c73cc1200ff509ff12ad04157f31348f4c6736Sunny Goyal sHandler = new Handler(thread.getLooper(), new LogWriterCallback()); 96713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 97713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 98713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return sHandler; 99713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 100713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 101713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal /** 102713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal * Blocks until all the pending logs are written to the disk 103713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal * @param out if not null, all the persisted logs are copied to the writer. 104713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal */ 105713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public static void flushAll(PrintWriter out) throws InterruptedException { 106713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (!ProviderConfig.IS_DOGFOOD_BUILD) { 107713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return; 108713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 109713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal CountDownLatch latch = new CountDownLatch(1); 110713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Message.obtain(getHandler(), LogWriterCallback.MSG_FLUSH, 111713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Pair.create(out, latch)).sendToTarget(); 112713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 113713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal latch.await(2, TimeUnit.SECONDS); 114713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 115713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 116713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal /** 117713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal * Writes logs to the file. 118713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal * Log files are named log-0 for even days of the year and log-1 for odd days of the year. 119713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal * Logs older than 36 hours are purged. 120713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal */ 121713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static class LogWriterCallback implements Handler.Callback { 122713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 123713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static final long CLOSE_DELAY = 5000; // 5 seconds 124713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 125713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static final int MSG_WRITE = 1; 126713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static final int MSG_CLOSE = 2; 127713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static final int MSG_FLUSH = 3; 128713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 129713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private String mCurrentFileName = null; 130713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private PrintWriter mCurrentWriter = null; 131713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 132713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private void closeWriter() { 133713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Utilities.closeSilently(mCurrentWriter); 134713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal mCurrentWriter = null; 135713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 136713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 137713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal @Override 138713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal public boolean handleMessage(Message msg) { 139713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (sLogsDirectory == null || !ProviderConfig.IS_DOGFOOD_BUILD) { 140713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return true; 141713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 142713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal switch (msg.what) { 143713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal case MSG_WRITE: { 144713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Calendar cal = Calendar.getInstance(); 145713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal // suffix with 0 or 1 based on the day of the year. 146713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) & 1); 147713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 148713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (!fileName.equals(mCurrentFileName)) { 149713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal closeWriter(); 150713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 151713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 152713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal try { 153713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (mCurrentWriter == null) { 154713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal mCurrentFileName = fileName; 155713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 156713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal boolean append = false; 157713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal File logFile = new File(sLogsDirectory, fileName); 158713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (logFile.exists()) { 159713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Calendar modifiedTime = Calendar.getInstance(); 160713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal modifiedTime.setTimeInMillis(logFile.lastModified()); 161713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 162713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal // If the file was modified more that 36 hours ago, purge the file. 163713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal // We use instead of 24 to account for day-365 followed by day-1 164713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal modifiedTime.add(Calendar.HOUR, 36); 165713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal append = cal.before(modifiedTime) 166713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal && logFile.length() < MAX_LOG_FILE_SIZE; 167713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 168713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal mCurrentWriter = new PrintWriter(new FileWriter(logFile, append)); 169713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 170713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 171713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal mCurrentWriter.println((String) msg.obj); 172713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal mCurrentWriter.flush(); 173713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 174713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal // Auto close file stream after some time. 175713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal sHandler.removeMessages(MSG_CLOSE); 176713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal sHandler.sendEmptyMessageDelayed(MSG_CLOSE, CLOSE_DELAY); 177713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } catch (Exception e) { 178713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Log.e("FileLog", "Error writing logs to file", e); 179713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal // Close stream, will try reopening during next log 180713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal closeWriter(); 181713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 182713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return true; 183713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 184713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal case MSG_CLOSE: { 185713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal closeWriter(); 186713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return true; 187713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 188713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal case MSG_FLUSH: { 189713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal closeWriter(); 190713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Pair<PrintWriter, CountDownLatch> p = 191713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal (Pair<PrintWriter, CountDownLatch>) msg.obj; 192713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 193713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (p.first != null) { 194713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal dumpFile(p.first, FILE_NAME_PREFIX + 0); 195713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal dumpFile(p.first, FILE_NAME_PREFIX + 1); 196713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 197713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal p.second.countDown(); 198713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return true; 199713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 200713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 201713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal return true; 202713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 203713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 204713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 205713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal private static void dumpFile(PrintWriter out, String fileName) { 206713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal File logFile = new File(sLogsDirectory, fileName); 207713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal if (logFile.exists()) { 208713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal 209713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal BufferedReader in = null; 210713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal try { 211713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal in = new BufferedReader(new FileReader(logFile)); 212713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal out.println(); 213713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal out.println("--- logfile: " + fileName + " ---"); 214713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal String line; 215713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal while ((line = in.readLine()) != null) { 216713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal out.println(line); 217713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 218713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } catch (Exception e) { 219713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal // ignore 220713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } finally { 221713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal Utilities.closeSilently(in); 222713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 223713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 224713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal } 225713edfce264db7edc409216d5c083f8dd6a7083fSunny Goyal} 226