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