blob: 5cbe5b958b7d084b57c099adf1794cdcc56e577b [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;
Makoto Onuki1f9bbc02018-10-24 15:49:49 -070023import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
Makoto Onukia9dca242017-06-21 17:06:49 -070026import android.os.SystemProperties;
27import android.text.format.DateUtils;
Makoto Onuki6a6ae042017-07-20 13:30:12 -070028import android.util.Log;
Makoto Onukia9dca242017-06-21 17:06:49 -070029import android.util.Slog;
30
31import com.android.internal.annotations.GuardedBy;
Makoto Onuki1f9bbc02018-10-24 15:49:49 -070032import com.android.internal.util.IntPair;
33import com.android.server.IoThread;
Makoto Onukia9dca242017-06-21 17:06:49 -070034
35import libcore.io.IoUtils;
36
37import java.io.BufferedReader;
38import java.io.File;
39import java.io.FileReader;
40import java.io.FileWriter;
41import java.io.IOException;
42import java.io.PrintWriter;
43import java.io.Reader;
44import java.io.Writer;
45import java.text.SimpleDateFormat;
46import java.util.Arrays;
47import java.util.Date;
48import java.util.concurrent.TimeUnit;
49
50/**
51 * Implements a rotating file logger for the sync manager, which is enabled only on userdebug/eng
52 * builds (unless debug.synclog is set to 1).
53 *
54 * Note this class could be used for other purposes too, but in general we don't want various
55 * system components to log to files, so it's put in a local package here.
56 */
57public class SyncLogger {
58 private static final String TAG = "SyncLogger";
59
60 private static SyncLogger sInstance;
61
Makoto Onukid4764302018-03-30 17:32:57 -070062 // Special UID used for logging to denote the self process.
63 public static final int CALLING_UID_SELF = -1;
64
Makoto Onukia9dca242017-06-21 17:06:49 -070065 SyncLogger() {
66 }
67
68 /**
69 * @return the singleton instance.
70 */
71 public static synchronized SyncLogger getInstance() {
72 if (sInstance == null) {
Makoto Onuki1f9bbc02018-10-24 15:49:49 -070073 final String flag = SystemProperties.get("debug.synclog");
Makoto Onukid4764302018-03-30 17:32:57 -070074 final boolean enable =
Makoto Onuki1f9bbc02018-10-24 15:49:49 -070075 (Build.IS_DEBUGGABLE
76 || "1".equals(flag)
77 || Log.isLoggable(TAG, Log.VERBOSE)) && !"0".equals(flag);
Makoto Onukia9dca242017-06-21 17:06:49 -070078 if (enable) {
79 sInstance = new RotatingFileLogger();
80 } else {
81 sInstance = new SyncLogger();
82 }
83 }
84 return sInstance;
85 }
86
87 /**
88 * Write strings to the log file.
89 */
90 public void log(Object... message) {
91 }
92
93 /**
94 * Remove old log files.
95 */
96 public void purgeOldLogs() {
97 // The default implementation is no-op.
98 }
99
100 public String jobParametersToString(JobParameters params) {
101 // The default implementation is no-op.
102 return "";
103 }
104
105 /**
106 * Dump all existing log files into a given writer.
107 */
108 public void dumpAll(PrintWriter pw) {
109 }
110
111 /**
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700112 * @return whether log is enabled or not.
113 */
114 public boolean enabled() {
115 return false;
116 }
117
118 /**
Makoto Onukia9dca242017-06-21 17:06:49 -0700119 * Actual implementation which is only used on userdebug/eng builds (by default).
120 */
121 private static class RotatingFileLogger extends SyncLogger {
122 private final Object mLock = new Object();
123
124 private final long mKeepAgeMs = TimeUnit.DAYS.toMillis(7);
125
126 private static final SimpleDateFormat sTimestampFormat
127 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
128
129 private static final SimpleDateFormat sFilenameDateFormat
130 = new SimpleDateFormat("yyyy-MM-dd");
131
132 @GuardedBy("mLock")
133 private final Date mCachedDate = new Date();
134
135 @GuardedBy("mLock")
136 private final StringBuilder mStringBuilder = new StringBuilder();
137
138 private final File mLogPath;
139
140 @GuardedBy("mLock")
141 private long mCurrentLogFileDayTimestamp;
142
143 @GuardedBy("mLock")
144 private Writer mLogWriter;
145
146 @GuardedBy("mLock")
147 private boolean mErrorShown;
148
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700149 private static final boolean DO_LOGCAT = Log.isLoggable(TAG, Log.DEBUG);
150
Makoto Onuki1f9bbc02018-10-24 15:49:49 -0700151 private final MyHandler mHandler;
152
Makoto Onukia9dca242017-06-21 17:06:49 -0700153 RotatingFileLogger() {
154 mLogPath = new File(Environment.getDataSystemDirectory(), "syncmanager-log");
Makoto Onuki1f9bbc02018-10-24 15:49:49 -0700155 mHandler = new MyHandler(IoThread.get().getLooper());
Makoto Onukia9dca242017-06-21 17:06:49 -0700156 }
157
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700158 @Override
159 public boolean enabled() {
160 return true;
161 }
162
Makoto Onukia9dca242017-06-21 17:06:49 -0700163 private void handleException(String message, Exception e) {
164 if (!mErrorShown) {
165 Slog.e(TAG, message, e);
166 mErrorShown = true;
167 }
168 }
169
170 @Override
171 public void log(Object... message) {
172 if (message == null) {
173 return;
174 }
Makoto Onuki1f9bbc02018-10-24 15:49:49 -0700175 final long now = System.currentTimeMillis();
176 mHandler.log(now, message);
177 }
178
179 void logInner(long now, Object[] message) {
Makoto Onukia9dca242017-06-21 17:06:49 -0700180 synchronized (mLock) {
Makoto Onukia9dca242017-06-21 17:06:49 -0700181 openLogLocked(now);
182 if (mLogWriter == null) {
183 return; // Couldn't open log file?
184 }
185
186 mStringBuilder.setLength(0);
187 mCachedDate.setTime(now);
188 mStringBuilder.append(sTimestampFormat.format(mCachedDate));
189 mStringBuilder.append(' ');
190
191 mStringBuilder.append(android.os.Process.myTid());
192 mStringBuilder.append(' ');
193
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700194 final int messageStart = mStringBuilder.length();
195
Makoto Onukia9dca242017-06-21 17:06:49 -0700196 for (Object o : message) {
197 mStringBuilder.append(o);
198 }
199 mStringBuilder.append('\n');
200
201 try {
202 mLogWriter.append(mStringBuilder);
203 mLogWriter.flush();
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700204
205 // Also write on logcat.
206 if (DO_LOGCAT) {
207 Log.d(TAG, mStringBuilder.substring(messageStart));
208 }
Makoto Onukia9dca242017-06-21 17:06:49 -0700209 } catch (IOException e) {
210 handleException("Failed to write log", e);
211 }
212 }
213 }
214
Andreas Gampea36dc622018-02-05 17:19:22 -0800215 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700216 private void openLogLocked(long now) {
217 // If we already have a log file opened and the date has't changed, just use it.
218 final long day = now % DateUtils.DAY_IN_MILLIS;
219 if ((mLogWriter != null) && (day == mCurrentLogFileDayTimestamp)) {
220 return;
221 }
222
223 // Otherwise create a new log file.
224 closeCurrentLogLocked();
225
226 mCurrentLogFileDayTimestamp = day;
227
228 mCachedDate.setTime(now);
229 final String filename = "synclog-" + sFilenameDateFormat.format(mCachedDate) + ".log";
230 final File file = new File(mLogPath, filename);
231
232 file.getParentFile().mkdirs();
233
234 try {
235 mLogWriter = new FileWriter(file, /* append= */ true);
236 } catch (IOException e) {
237 handleException("Failed to open log file: " + file, e);
238 }
239 }
240
Andreas Gampea36dc622018-02-05 17:19:22 -0800241 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700242 private void closeCurrentLogLocked() {
243 IoUtils.closeQuietly(mLogWriter);
244 mLogWriter = null;
245 }
246
247 @Override
248 public void purgeOldLogs() {
249 synchronized (mLock) {
250 FileUtils.deleteOlderFiles(mLogPath, /* keepCount= */ 1, mKeepAgeMs);
251 }
252 }
253
254 @Override
255 public String jobParametersToString(JobParameters params) {
Makoto Onuki5397be12017-12-20 16:22:34 +0900256 return SyncJobService.jobParametersToString(params);
Makoto Onukia9dca242017-06-21 17:06:49 -0700257 }
258
259 @Override
260 public void dumpAll(PrintWriter pw) {
261 synchronized (mLock) {
262 final String[] files = mLogPath.list();
263 if (files == null || (files.length == 0)) {
264 return;
265 }
266 Arrays.sort(files);
267
268 for (String file : files) {
269 dumpFile(pw, new File(mLogPath, file));
270 }
271 }
272 }
273
274 private void dumpFile(PrintWriter pw, File file) {
275 Slog.w(TAG, "Dumping " + file);
276 final char[] buffer = new char[32 * 1024];
277
278 try (Reader in = new BufferedReader(new FileReader(file))) {
279 int read;
280 while ((read = in.read(buffer)) >= 0) {
281 if (read > 0) {
282 pw.write(buffer, 0, read);
283 }
284 }
285 } catch (IOException e) {
286 }
287 }
Makoto Onuki1f9bbc02018-10-24 15:49:49 -0700288
289 private class MyHandler extends Handler {
290 public static final int MSG_LOG_ID = 1;
291
292 MyHandler(Looper looper) {
293 super(looper);
294 }
295
296 public void log(long now, Object[] message) {
297 obtainMessage(MSG_LOG_ID, IntPair.first(now), IntPair.second(now), message)
298 .sendToTarget();
299 }
300
301 @Override
302 public void handleMessage(Message msg) {
303 switch (msg.what) {
304 case MSG_LOG_ID: {
305 logInner(IntPair.of(msg.arg1, msg.arg2), (Object[]) msg.obj);
306 break;
307 }
308 }
309 }
310 }
Makoto Onukia9dca242017-06-21 17:06:49 -0700311 }
312}