blob: 20aec7e4ae29d6c10be01fae44e6de664741fdb0 [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
Andreas Gampea36dc622018-02-05 17:19:22 -0800197 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700198 private void openLogLocked(long now) {
199 // If we already have a log file opened and the date has't changed, just use it.
200 final long day = now % DateUtils.DAY_IN_MILLIS;
201 if ((mLogWriter != null) && (day == mCurrentLogFileDayTimestamp)) {
202 return;
203 }
204
205 // Otherwise create a new log file.
206 closeCurrentLogLocked();
207
208 mCurrentLogFileDayTimestamp = day;
209
210 mCachedDate.setTime(now);
211 final String filename = "synclog-" + sFilenameDateFormat.format(mCachedDate) + ".log";
212 final File file = new File(mLogPath, filename);
213
214 file.getParentFile().mkdirs();
215
216 try {
217 mLogWriter = new FileWriter(file, /* append= */ true);
218 } catch (IOException e) {
219 handleException("Failed to open log file: " + file, e);
220 }
221 }
222
Andreas Gampea36dc622018-02-05 17:19:22 -0800223 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700224 private void closeCurrentLogLocked() {
225 IoUtils.closeQuietly(mLogWriter);
226 mLogWriter = null;
227 }
228
229 @Override
230 public void purgeOldLogs() {
231 synchronized (mLock) {
232 FileUtils.deleteOlderFiles(mLogPath, /* keepCount= */ 1, mKeepAgeMs);
233 }
234 }
235
236 @Override
237 public String jobParametersToString(JobParameters params) {
Makoto Onuki5397be12017-12-20 16:22:34 +0900238 return SyncJobService.jobParametersToString(params);
Makoto Onukia9dca242017-06-21 17:06:49 -0700239 }
240
241 @Override
242 public void dumpAll(PrintWriter pw) {
243 synchronized (mLock) {
244 final String[] files = mLogPath.list();
245 if (files == null || (files.length == 0)) {
246 return;
247 }
248 Arrays.sort(files);
249
250 for (String file : files) {
251 dumpFile(pw, new File(mLogPath, file));
252 }
253 }
254 }
255
256 private void dumpFile(PrintWriter pw, File file) {
257 Slog.w(TAG, "Dumping " + file);
258 final char[] buffer = new char[32 * 1024];
259
260 try (Reader in = new BufferedReader(new FileReader(file))) {
261 int read;
262 while ((read = in.read(buffer)) >= 0) {
263 if (read > 0) {
264 pw.write(buffer, 0, read);
265 }
266 }
267 } catch (IOException e) {
268 }
269 }
270 }
271}