Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2017 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.server.content;
     18 
     19 import android.app.job.JobParameters;
     20 import android.os.Build;
     21 import android.os.Environment;
     22 import android.os.FileUtils;
     23 import android.os.SystemProperties;
     24 import android.text.format.DateUtils;
     25 import android.util.Log;
     26 import android.util.Slog;
     27 
     28 import com.android.internal.annotations.GuardedBy;
     29 
     30 import libcore.io.IoUtils;
     31 
     32 import java.io.BufferedReader;
     33 import java.io.File;
     34 import java.io.FileReader;
     35 import java.io.FileWriter;
     36 import java.io.IOException;
     37 import java.io.PrintWriter;
     38 import java.io.Reader;
     39 import java.io.Writer;
     40 import java.text.SimpleDateFormat;
     41 import java.util.Arrays;
     42 import java.util.Date;
     43 import java.util.concurrent.TimeUnit;
     44 
     45 /**
     46  * Implements a rotating file logger for the sync manager, which is enabled only on userdebug/eng
     47  * builds (unless debug.synclog is set to 1).
     48  *
     49  * Note this class could be used for other purposes too, but in general we don't want various
     50  * system components to log to files, so it's put in a local package here.
     51  */
     52 public class SyncLogger {
     53     private static final String TAG = "SyncLogger";
     54 
     55     private static SyncLogger sInstance;
     56 
     57     // Special UID used for logging to denote the self process.
     58     public static final int CALLING_UID_SELF = -1;
     59 
     60     SyncLogger() {
     61     }
     62 
     63     /**
     64      * @return the singleton instance.
     65      */
     66     public static synchronized SyncLogger getInstance() {
     67         if (sInstance == null) {
     68             final boolean enable =
     69                     Build.IS_DEBUGGABLE
     70                     || "1".equals(SystemProperties.get("debug.synclog"))
     71                     || Log.isLoggable(TAG, Log.VERBOSE);
     72             if (enable) {
     73                 sInstance = new RotatingFileLogger();
     74             } else {
     75                 sInstance = new SyncLogger();
     76             }
     77         }
     78         return sInstance;
     79     }
     80 
     81     /**
     82      * Write strings to the log file.
     83      */
     84     public void log(Object... message) {
     85     }
     86 
     87     /**
     88      * Remove old log files.
     89      */
     90     public void purgeOldLogs() {
     91         // The default implementation is no-op.
     92     }
     93 
     94     public String jobParametersToString(JobParameters params) {
     95         // The default implementation is no-op.
     96         return "";
     97     }
     98 
     99     /**
    100      * Dump all existing log files into a given writer.
    101      */
    102     public void dumpAll(PrintWriter pw) {
    103     }
    104 
    105     /**
    106      * @return whether log is enabled or not.
    107      */
    108     public boolean enabled() {
    109         return false;
    110     }
    111 
    112     /**
    113      * Actual implementation which is only used on userdebug/eng builds (by default).
    114      */
    115     private static class RotatingFileLogger extends SyncLogger {
    116         private final Object mLock = new Object();
    117 
    118         private final long mKeepAgeMs = TimeUnit.DAYS.toMillis(7);
    119 
    120         private static final SimpleDateFormat sTimestampFormat
    121                 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    122 
    123         private static final SimpleDateFormat sFilenameDateFormat
    124                 = new SimpleDateFormat("yyyy-MM-dd");
    125 
    126         @GuardedBy("mLock")
    127         private final Date mCachedDate = new Date();
    128 
    129         @GuardedBy("mLock")
    130         private final StringBuilder mStringBuilder = new StringBuilder();
    131 
    132         private final File mLogPath;
    133 
    134         @GuardedBy("mLock")
    135         private long mCurrentLogFileDayTimestamp;
    136 
    137         @GuardedBy("mLock")
    138         private Writer mLogWriter;
    139 
    140         @GuardedBy("mLock")
    141         private boolean mErrorShown;
    142 
    143         private static final boolean DO_LOGCAT = Log.isLoggable(TAG, Log.DEBUG);
    144 
    145         RotatingFileLogger() {
    146             mLogPath = new File(Environment.getDataSystemDirectory(), "syncmanager-log");
    147         }
    148 
    149         @Override
    150         public boolean enabled() {
    151             return true;
    152         }
    153 
    154         private void handleException(String message, Exception e) {
    155             if (!mErrorShown) {
    156                 Slog.e(TAG, message, e);
    157                 mErrorShown = true;
    158             }
    159         }
    160 
    161         @Override
    162         public void log(Object... message) {
    163             if (message == null) {
    164                 return;
    165             }
    166             synchronized (mLock) {
    167                 final long now = System.currentTimeMillis();
    168                 openLogLocked(now);
    169                 if (mLogWriter == null) {
    170                     return; // Couldn't open log file?
    171                 }
    172 
    173                 mStringBuilder.setLength(0);
    174                 mCachedDate.setTime(now);
    175                 mStringBuilder.append(sTimestampFormat.format(mCachedDate));
    176                 mStringBuilder.append(' ');
    177 
    178                 mStringBuilder.append(android.os.Process.myTid());
    179                 mStringBuilder.append(' ');
    180 
    181                 final int messageStart = mStringBuilder.length();
    182 
    183                 for (Object o : message) {
    184                     mStringBuilder.append(o);
    185                 }
    186                 mStringBuilder.append('\n');
    187 
    188                 try {
    189                     mLogWriter.append(mStringBuilder);
    190                     mLogWriter.flush();
    191 
    192                     // Also write on logcat.
    193                     if (DO_LOGCAT) {
    194                         Log.d(TAG, mStringBuilder.substring(messageStart));
    195                     }
    196                 } catch (IOException e) {
    197                     handleException("Failed to write log", e);
    198                 }
    199             }
    200         }
    201 
    202         @GuardedBy("mLock")
    203         private void openLogLocked(long now) {
    204             // If we already have a log file opened and the date has't changed, just use it.
    205             final long day = now % DateUtils.DAY_IN_MILLIS;
    206             if ((mLogWriter != null) && (day == mCurrentLogFileDayTimestamp)) {
    207                 return;
    208             }
    209 
    210             // Otherwise create a new log file.
    211             closeCurrentLogLocked();
    212 
    213             mCurrentLogFileDayTimestamp = day;
    214 
    215             mCachedDate.setTime(now);
    216             final String filename = "synclog-" + sFilenameDateFormat.format(mCachedDate) + ".log";
    217             final File file = new File(mLogPath, filename);
    218 
    219             file.getParentFile().mkdirs();
    220 
    221             try {
    222                 mLogWriter = new FileWriter(file, /* append= */ true);
    223             } catch (IOException e) {
    224                 handleException("Failed to open log file: " + file, e);
    225             }
    226         }
    227 
    228         @GuardedBy("mLock")
    229         private void closeCurrentLogLocked() {
    230             IoUtils.closeQuietly(mLogWriter);
    231             mLogWriter = null;
    232         }
    233 
    234         @Override
    235         public void purgeOldLogs() {
    236             synchronized (mLock) {
    237                 FileUtils.deleteOlderFiles(mLogPath, /* keepCount= */ 1, mKeepAgeMs);
    238             }
    239         }
    240 
    241         @Override
    242         public String jobParametersToString(JobParameters params) {
    243             return SyncJobService.jobParametersToString(params);
    244         }
    245 
    246         @Override
    247         public void dumpAll(PrintWriter pw) {
    248             synchronized (mLock) {
    249                 final String[] files = mLogPath.list();
    250                 if (files == null || (files.length == 0)) {
    251                     return;
    252                 }
    253                 Arrays.sort(files);
    254 
    255                 for (String file : files) {
    256                     dumpFile(pw, new File(mLogPath, file));
    257                 }
    258             }
    259         }
    260 
    261         private void dumpFile(PrintWriter pw, File file) {
    262             Slog.w(TAG, "Dumping " + file);
    263             final char[] buffer = new char[32 * 1024];
    264 
    265             try (Reader in = new BufferedReader(new FileReader(file))) {
    266                 int read;
    267                 while ((read = in.read(buffer)) >= 0) {
    268                     if (read > 0) {
    269                         pw.write(buffer, 0, read);
    270                     }
    271                 }
    272             } catch (IOException e) {
    273             }
    274         }
    275     }
    276 }
    277