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