Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 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.messaging.util;
     18 
     19 import android.os.Process;
     20 import android.util.Log;
     21 
     22 import com.android.messaging.Factory;
     23 
     24 import java.io.BufferedReader;
     25 import java.io.File;
     26 import java.io.FileNotFoundException;
     27 import java.io.FileReader;
     28 import java.io.IOException;
     29 import java.io.PrintWriter;
     30 import java.text.SimpleDateFormat;
     31 import java.util.logging.FileHandler;
     32 import java.util.logging.Formatter;
     33 import java.util.logging.Handler;
     34 import java.util.logging.Level;
     35 import java.util.logging.Logger;
     36 
     37 /**
     38  * Save the app's own log to dump along with adb bugreport
     39  */
     40 public abstract class LogSaver {
     41     /**
     42      * Writes the accumulated log entries, from oldest to newest, to the specified PrintWriter.
     43      * Log lines are emitted in much the same form as logcat -v threadtime -- specifically,
     44      * lines will include a timestamp, pid, tid, level, and tag.
     45      *
     46      * @param writer The PrintWriter to output
     47      */
     48     public abstract void dump(PrintWriter writer);
     49 
     50     /**
     51      * Log a line
     52      *
     53      * @param level The log level to use
     54      * @param tag The log tag
     55      * @param msg The message of the log line
     56      */
     57     public abstract void log(int level, String tag, String msg);
     58 
     59     /**
     60      * Check if the LogSaver still matches the current Gservices settings
     61      *
     62      * @return true if matches, false otherwise
     63      */
     64     public abstract boolean isCurrent();
     65 
     66     private LogSaver() {
     67     }
     68 
     69     public static LogSaver newInstance() {
     70         final boolean persistent = BugleGservices.get().getBoolean(
     71                 BugleGservicesKeys.PERSISTENT_LOGSAVER,
     72                 BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
     73         if (persistent) {
     74             final int setSize = BugleGservices.get().getInt(
     75                     BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
     76                     BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
     77             final int fileLimitBytes = BugleGservices.get().getInt(
     78                     BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
     79                     BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
     80             return new DiskLogSaver(setSize, fileLimitBytes);
     81         } else {
     82             final int size = BugleGservices.get().getInt(
     83                     BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
     84                     BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
     85             return new MemoryLogSaver(size);
     86         }
     87     }
     88 
     89     /**
     90      * A circular in-memory log to be used to log potentially verbose logs. The logs will be
     91      * persisted in memory in the application and can be dumped by various dump() methods.
     92      * For example, adb shell dumpsys activity provider com.android.messaging.
     93      * The dump will also show up in bugreports.
     94      */
     95     private static final class MemoryLogSaver extends LogSaver {
     96         /**
     97          * Record to store a single log entry. Stores timestamp, tid, level, tag, and message.
     98          * It can be reused when the circular log rolls over. This avoids creating new objects.
     99          */
    100         private static class LogRecord {
    101             int mTid;
    102             String mLevelString;
    103             long mTimeMillis;     // from System.currentTimeMillis
    104             String mTag;
    105             String mMessage;
    106 
    107             LogRecord() {
    108             }
    109 
    110             void set(int tid, int level, long time, String tag, String message) {
    111                 this.mTid = tid;
    112                 this.mTimeMillis = time;
    113                 this.mTag = tag;
    114                 this.mMessage = message;
    115                 this.mLevelString = getLevelString(level);
    116             }
    117         }
    118 
    119         private final int mSize;
    120         private final CircularArray<LogRecord> mLogList;
    121         private final Object mLock;
    122 
    123         private final SimpleDateFormat mSdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
    124 
    125         public MemoryLogSaver(final int size) {
    126             mSize = size;
    127             mLogList = new CircularArray<LogRecord>(size);
    128             mLock = new Object();
    129         }
    130 
    131         @Override
    132         public void dump(PrintWriter writer) {
    133             int pid = Process.myPid();
    134             synchronized (mLock) {
    135                 for (int i = 0; i < mLogList.count(); i++) {
    136                     LogRecord rec = mLogList.get(i);
    137                     writer.println(String.format("%s %5d %5d %s %s: %s",
    138                             mSdf.format(rec.mTimeMillis),
    139                             pid, rec.mTid, rec.mLevelString, rec.mTag, rec.mMessage));
    140                 }
    141             }
    142         }
    143 
    144         @Override
    145         public void log(int level, String tag, String msg) {
    146             synchronized (mLock) {
    147                 LogRecord rec = mLogList.getFree();
    148                 if (rec == null) {
    149                     rec = new LogRecord();
    150                 }
    151                 rec.set(Process.myTid(), level, System.currentTimeMillis(), tag, msg);
    152                 mLogList.add(rec);
    153             }
    154         }
    155 
    156         @Override
    157         public boolean isCurrent() {
    158             final boolean persistent = BugleGservices.get().getBoolean(
    159                     BugleGservicesKeys.PERSISTENT_LOGSAVER,
    160                     BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
    161             if (persistent) {
    162                 return false;
    163             }
    164             final int size = BugleGservices.get().getInt(
    165                     BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
    166                     BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
    167             return size == mSize;
    168         }
    169     }
    170 
    171     /**
    172      * A persistent, on-disk log saver. It uses the standard Java util logger along with
    173      * a rotation log file set to store the logs in app's local file directory "app_logs".
    174      */
    175     private static final class DiskLogSaver extends LogSaver {
    176         private static final String DISK_LOG_DIR_NAME = "logs";
    177 
    178         private final int mSetSize;
    179         private final int mFileLimitBytes;
    180         private Logger mDiskLogger;
    181 
    182         public DiskLogSaver(final int setSize, final int fileLimitBytes) {
    183             Assert.isTrue(setSize > 0);
    184             Assert.isTrue(fileLimitBytes > 0);
    185             mSetSize = setSize;
    186             mFileLimitBytes = fileLimitBytes;
    187             initDiskLog();
    188         }
    189 
    190         private static void clearDefaultHandlers(Logger logger) {
    191             Assert.notNull(logger);
    192             for (Handler handler : logger.getHandlers()) {
    193                 logger.removeHandler(handler);
    194             }
    195         }
    196 
    197         private void initDiskLog() {
    198             mDiskLogger = Logger.getLogger(LogUtil.BUGLE_TAG);
    199             // We don't want the default console handler
    200             clearDefaultHandlers(mDiskLogger);
    201             // Don't want duplicate print in system log
    202             mDiskLogger.setUseParentHandlers(false);
    203             // FileHandler manages the log files in a fixed rotation set
    204             final File logDir = Factory.get().getApplicationContext().getDir(
    205                     DISK_LOG_DIR_NAME, 0/*mode*/);
    206             FileHandler handler = null;
    207             try {
    208                 handler = new FileHandler(
    209                         logDir + "/%g.log", mFileLimitBytes, mSetSize, true/*append*/);
    210             } catch (Exception e) {
    211                 Log.e(LogUtil.BUGLE_TAG, "LogSaver: fail to init disk logger", e);
    212                 return;
    213             }
    214             final Formatter formatter = new Formatter() {
    215                 @Override
    216                 public String format(java.util.logging.LogRecord r) {
    217                     return r.getMessage();
    218                 }
    219             };
    220             handler.setFormatter(formatter);
    221             handler.setLevel(Level.ALL);
    222             mDiskLogger.addHandler(handler);
    223         }
    224 
    225         @Override
    226         public void dump(PrintWriter writer) {
    227             for (int i = mSetSize - 1; i >= 0; i--) {
    228                 final File logDir = Factory.get().getApplicationContext().getDir(
    229                         DISK_LOG_DIR_NAME, 0/*mode*/);
    230                 final String logFilePath = logDir + "/" + i + ".log";
    231                 try {
    232                     final File logFile = new File(logFilePath);
    233                     if (!logFile.exists()) {
    234                         continue;
    235                     }
    236                     final BufferedReader reader = new BufferedReader(new FileReader(logFile));
    237                     for (String line; (line = reader.readLine()) != null;) {
    238                         line = line.trim();
    239                         writer.println(line);
    240                     }
    241                 } catch (FileNotFoundException e) {
    242                     Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not find log file " + logFilePath);
    243                 } catch (IOException e) {
    244                     Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not read log file", e);
    245                 }
    246             }
    247         }
    248 
    249         @Override
    250         public void log(int level, String tag, String msg) {
    251             final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
    252             mDiskLogger.info(String.format("%s %5d %5d %s %s: %s\n",
    253                     sdf.format(System.currentTimeMillis()),
    254                     Process.myPid(), Process.myTid(), getLevelString(level), tag, msg));
    255         }
    256 
    257         @Override
    258         public boolean isCurrent() {
    259             final boolean persistent = BugleGservices.get().getBoolean(
    260                     BugleGservicesKeys.PERSISTENT_LOGSAVER,
    261                     BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
    262             if (!persistent) {
    263                 return false;
    264             }
    265             final int setSize = BugleGservices.get().getInt(
    266                     BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
    267                     BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
    268             final int fileLimitBytes = BugleGservices.get().getInt(
    269                     BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
    270                     BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
    271             return setSize == mSetSize && fileLimitBytes == mFileLimitBytes;
    272         }
    273     }
    274 
    275     private static String getLevelString(final int level) {
    276         switch (level) {
    277             case android.util.Log.DEBUG:
    278                 return "D";
    279             case android.util.Log.WARN:
    280                 return "W";
    281             case android.util.Log.INFO:
    282                 return "I";
    283             case android.util.Log.VERBOSE:
    284                 return "V";
    285             case android.util.Log.ERROR:
    286                 return "E";
    287             case android.util.Log.ASSERT:
    288                 return "A";
    289             default:
    290                 return "?";
    291         }
    292     }
    293 }
    294