blob: 8c35e27671eb891c211940aae0ce4d30a30889da [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
Makoto Onukid4764302018-03-30 17:32:57 -070057 // Special UID used for logging to denote the self process.
58 public static final int CALLING_UID_SELF = -1;
59
Makoto Onukia9dca242017-06-21 17:06:49 -070060 SyncLogger() {
61 }
62
63 /**
64 * @return the singleton instance.
65 */
66 public static synchronized SyncLogger getInstance() {
67 if (sInstance == null) {
Makoto Onukid4764302018-03-30 17:32:57 -070068 final boolean enable =
69 Build.IS_DEBUGGABLE
70 || "1".equals(SystemProperties.get("debug.synclog"))
71 || Log.isLoggable(TAG, Log.VERBOSE);
Makoto Onukia9dca242017-06-21 17:06:49 -070072 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 /**
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700106 * @return whether log is enabled or not.
107 */
108 public boolean enabled() {
109 return false;
110 }
111
112 /**
Makoto Onukia9dca242017-06-21 17:06:49 -0700113 * 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
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700143 private static final boolean DO_LOGCAT = Log.isLoggable(TAG, Log.DEBUG);
144
Makoto Onukia9dca242017-06-21 17:06:49 -0700145 RotatingFileLogger() {
146 mLogPath = new File(Environment.getDataSystemDirectory(), "syncmanager-log");
147 }
148
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700149 @Override
150 public boolean enabled() {
151 return true;
152 }
153
Makoto Onukia9dca242017-06-21 17:06:49 -0700154 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
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700181 final int messageStart = mStringBuilder.length();
182
Makoto Onukia9dca242017-06-21 17:06:49 -0700183 for (Object o : message) {
184 mStringBuilder.append(o);
185 }
186 mStringBuilder.append('\n');
187
188 try {
189 mLogWriter.append(mStringBuilder);
190 mLogWriter.flush();
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700191
192 // Also write on logcat.
193 if (DO_LOGCAT) {
194 Log.d(TAG, mStringBuilder.substring(messageStart));
195 }
Makoto Onukia9dca242017-06-21 17:06:49 -0700196 } catch (IOException e) {
197 handleException("Failed to write log", e);
198 }
199 }
200 }
201
Andreas Gampea36dc622018-02-05 17:19:22 -0800202 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700203 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
Andreas Gampea36dc622018-02-05 17:19:22 -0800228 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700229 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) {
Makoto Onuki5397be12017-12-20 16:22:34 +0900243 return SyncJobService.jobParametersToString(params);
Makoto Onukia9dca242017-06-21 17:06:49 -0700244 }
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}