blob: 75c01819a9eaa682eb10e72664b1897a0aff6062 [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 /**
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700101 * @return whether log is enabled or not.
102 */
103 public boolean enabled() {
104 return false;
105 }
106
107 /**
Makoto Onukia9dca242017-06-21 17:06:49 -0700108 * Actual implementation which is only used on userdebug/eng builds (by default).
109 */
110 private static class RotatingFileLogger extends SyncLogger {
111 private final Object mLock = new Object();
112
113 private final long mKeepAgeMs = TimeUnit.DAYS.toMillis(7);
114
115 private static final SimpleDateFormat sTimestampFormat
116 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
117
118 private static final SimpleDateFormat sFilenameDateFormat
119 = new SimpleDateFormat("yyyy-MM-dd");
120
121 @GuardedBy("mLock")
122 private final Date mCachedDate = new Date();
123
124 @GuardedBy("mLock")
125 private final StringBuilder mStringBuilder = new StringBuilder();
126
127 private final File mLogPath;
128
129 @GuardedBy("mLock")
130 private long mCurrentLogFileDayTimestamp;
131
132 @GuardedBy("mLock")
133 private Writer mLogWriter;
134
135 @GuardedBy("mLock")
136 private boolean mErrorShown;
137
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700138 private static final boolean DO_LOGCAT = Log.isLoggable(TAG, Log.DEBUG);
139
Makoto Onukia9dca242017-06-21 17:06:49 -0700140 RotatingFileLogger() {
141 mLogPath = new File(Environment.getDataSystemDirectory(), "syncmanager-log");
142 }
143
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700144 @Override
145 public boolean enabled() {
146 return true;
147 }
148
Makoto Onukia9dca242017-06-21 17:06:49 -0700149 private void handleException(String message, Exception e) {
150 if (!mErrorShown) {
151 Slog.e(TAG, message, e);
152 mErrorShown = true;
153 }
154 }
155
156 @Override
157 public void log(Object... message) {
158 if (message == null) {
159 return;
160 }
161 synchronized (mLock) {
162 final long now = System.currentTimeMillis();
163 openLogLocked(now);
164 if (mLogWriter == null) {
165 return; // Couldn't open log file?
166 }
167
168 mStringBuilder.setLength(0);
169 mCachedDate.setTime(now);
170 mStringBuilder.append(sTimestampFormat.format(mCachedDate));
171 mStringBuilder.append(' ');
172
173 mStringBuilder.append(android.os.Process.myTid());
174 mStringBuilder.append(' ');
175
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700176 final int messageStart = mStringBuilder.length();
177
Makoto Onukia9dca242017-06-21 17:06:49 -0700178 for (Object o : message) {
179 mStringBuilder.append(o);
180 }
181 mStringBuilder.append('\n');
182
183 try {
184 mLogWriter.append(mStringBuilder);
185 mLogWriter.flush();
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700186
187 // Also write on logcat.
188 if (DO_LOGCAT) {
189 Log.d(TAG, mStringBuilder.substring(messageStart));
190 }
Makoto Onukia9dca242017-06-21 17:06:49 -0700191 } catch (IOException e) {
192 handleException("Failed to write log", e);
193 }
194 }
195 }
196
197 private void openLogLocked(long now) {
198 // If we already have a log file opened and the date has't changed, just use it.
199 final long day = now % DateUtils.DAY_IN_MILLIS;
200 if ((mLogWriter != null) && (day == mCurrentLogFileDayTimestamp)) {
201 return;
202 }
203
204 // Otherwise create a new log file.
205 closeCurrentLogLocked();
206
207 mCurrentLogFileDayTimestamp = day;
208
209 mCachedDate.setTime(now);
210 final String filename = "synclog-" + sFilenameDateFormat.format(mCachedDate) + ".log";
211 final File file = new File(mLogPath, filename);
212
213 file.getParentFile().mkdirs();
214
215 try {
216 mLogWriter = new FileWriter(file, /* append= */ true);
217 } catch (IOException e) {
218 handleException("Failed to open log file: " + file, e);
219 }
220 }
221
222 private void closeCurrentLogLocked() {
223 IoUtils.closeQuietly(mLogWriter);
224 mLogWriter = null;
225 }
226
227 @Override
228 public void purgeOldLogs() {
229 synchronized (mLock) {
230 FileUtils.deleteOlderFiles(mLogPath, /* keepCount= */ 1, mKeepAgeMs);
231 }
232 }
233
234 @Override
235 public String jobParametersToString(JobParameters params) {
Makoto Onuki5397be12017-12-20 16:22:34 +0900236 return SyncJobService.jobParametersToString(params);
Makoto Onukia9dca242017-06-21 17:06:49 -0700237 }
238
239 @Override
240 public void dumpAll(PrintWriter pw) {
241 synchronized (mLock) {
242 final String[] files = mLogPath.list();
243 if (files == null || (files.length == 0)) {
244 return;
245 }
246 Arrays.sort(files);
247
248 for (String file : files) {
249 dumpFile(pw, new File(mLogPath, file));
250 }
251 }
252 }
253
254 private void dumpFile(PrintWriter pw, File file) {
255 Slog.w(TAG, "Dumping " + file);
256 final char[] buffer = new char[32 * 1024];
257
258 try (Reader in = new BufferedReader(new FileReader(file))) {
259 int read;
260 while ((read = in.read(buffer)) >= 0) {
261 if (read > 0) {
262 pw.write(buffer, 0, read);
263 }
264 }
265 } catch (IOException e) {
266 }
267 }
268 }
269}