blob: b714077aab0fb120a1cda4331662076663192119 [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
Makoto Onuki5fc60022019-01-15 09:34:05 -080019import android.accounts.Account;
Makoto Onukia9dca242017-06-21 17:06:49 -070020import android.app.job.JobParameters;
21import android.os.Build;
22import android.os.Environment;
23import android.os.FileUtils;
24import android.os.SystemProperties;
25import android.text.format.DateUtils;
Makoto Onuki6a6ae042017-07-20 13:30:12 -070026import android.util.Log;
Makoto Onukia9dca242017-06-21 17:06:49 -070027import android.util.Slog;
28
29import com.android.internal.annotations.GuardedBy;
Makoto Onuki5fc60022019-01-15 09:34:05 -080030import com.android.server.content.SyncManager.ActiveSyncContext;
31import com.android.server.content.SyncStorageEngine.EndPoint;
Makoto Onukia9dca242017-06-21 17:06:49 -070032
33import libcore.io.IoUtils;
34
35import java.io.BufferedReader;
36import java.io.File;
37import java.io.FileReader;
38import java.io.FileWriter;
39import java.io.IOException;
40import java.io.PrintWriter;
41import java.io.Reader;
42import java.io.Writer;
43import java.text.SimpleDateFormat;
44import java.util.Arrays;
45import java.util.Date;
46import java.util.concurrent.TimeUnit;
47
48/**
49 * Implements a rotating file logger for the sync manager, which is enabled only on userdebug/eng
50 * builds (unless debug.synclog is set to 1).
51 *
52 * Note this class could be used for other purposes too, but in general we don't want various
53 * system components to log to files, so it's put in a local package here.
54 */
55public class SyncLogger {
56 private static final String TAG = "SyncLogger";
57
58 private static SyncLogger sInstance;
59
Makoto Onukid4764302018-03-30 17:32:57 -070060 // Special UID used for logging to denote the self process.
61 public static final int CALLING_UID_SELF = -1;
62
Makoto Onukia9dca242017-06-21 17:06:49 -070063 SyncLogger() {
64 }
65
66 /**
67 * @return the singleton instance.
68 */
69 public static synchronized SyncLogger getInstance() {
70 if (sInstance == null) {
Makoto Onukid4764302018-03-30 17:32:57 -070071 final boolean enable =
72 Build.IS_DEBUGGABLE
73 || "1".equals(SystemProperties.get("debug.synclog"))
74 || Log.isLoggable(TAG, Log.VERBOSE);
Makoto Onukia9dca242017-06-21 17:06:49 -070075 if (enable) {
76 sInstance = new RotatingFileLogger();
77 } else {
78 sInstance = new SyncLogger();
79 }
80 }
81 return sInstance;
82 }
83
84 /**
85 * Write strings to the log file.
86 */
87 public void log(Object... message) {
88 }
89
90 /**
91 * Remove old log files.
92 */
93 public void purgeOldLogs() {
94 // The default implementation is no-op.
95 }
96
97 public String jobParametersToString(JobParameters params) {
98 // The default implementation is no-op.
99 return "";
100 }
101
102 /**
103 * Dump all existing log files into a given writer.
104 */
105 public void dumpAll(PrintWriter pw) {
106 }
107
108 /**
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700109 * @return whether log is enabled or not.
110 */
111 public boolean enabled() {
112 return false;
113 }
114
115 /**
Makoto Onukia9dca242017-06-21 17:06:49 -0700116 * Actual implementation which is only used on userdebug/eng builds (by default).
117 */
118 private static class RotatingFileLogger extends SyncLogger {
119 private final Object mLock = new Object();
120
121 private final long mKeepAgeMs = TimeUnit.DAYS.toMillis(7);
122
123 private static final SimpleDateFormat sTimestampFormat
124 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
125
126 private static final SimpleDateFormat sFilenameDateFormat
127 = new SimpleDateFormat("yyyy-MM-dd");
128
129 @GuardedBy("mLock")
130 private final Date mCachedDate = new Date();
131
132 @GuardedBy("mLock")
133 private final StringBuilder mStringBuilder = new StringBuilder();
134
135 private final File mLogPath;
136
137 @GuardedBy("mLock")
138 private long mCurrentLogFileDayTimestamp;
139
140 @GuardedBy("mLock")
141 private Writer mLogWriter;
142
143 @GuardedBy("mLock")
144 private boolean mErrorShown;
145
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700146 private static final boolean DO_LOGCAT = Log.isLoggable(TAG, Log.DEBUG);
147
Makoto Onukia9dca242017-06-21 17:06:49 -0700148 RotatingFileLogger() {
149 mLogPath = new File(Environment.getDataSystemDirectory(), "syncmanager-log");
150 }
151
Makoto Onukibbf6d8c2017-08-11 12:11:39 -0700152 @Override
153 public boolean enabled() {
154 return true;
155 }
156
Makoto Onukia9dca242017-06-21 17:06:49 -0700157 private void handleException(String message, Exception e) {
158 if (!mErrorShown) {
159 Slog.e(TAG, message, e);
160 mErrorShown = true;
161 }
162 }
163
164 @Override
165 public void log(Object... message) {
166 if (message == null) {
167 return;
168 }
169 synchronized (mLock) {
170 final long now = System.currentTimeMillis();
171 openLogLocked(now);
172 if (mLogWriter == null) {
173 return; // Couldn't open log file?
174 }
175
176 mStringBuilder.setLength(0);
177 mCachedDate.setTime(now);
178 mStringBuilder.append(sTimestampFormat.format(mCachedDate));
179 mStringBuilder.append(' ');
180
181 mStringBuilder.append(android.os.Process.myTid());
182 mStringBuilder.append(' ');
183
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700184 final int messageStart = mStringBuilder.length();
185
Makoto Onukia9dca242017-06-21 17:06:49 -0700186 for (Object o : message) {
187 mStringBuilder.append(o);
188 }
189 mStringBuilder.append('\n');
190
191 try {
192 mLogWriter.append(mStringBuilder);
193 mLogWriter.flush();
Makoto Onuki6a6ae042017-07-20 13:30:12 -0700194
195 // Also write on logcat.
196 if (DO_LOGCAT) {
197 Log.d(TAG, mStringBuilder.substring(messageStart));
198 }
Makoto Onukia9dca242017-06-21 17:06:49 -0700199 } catch (IOException e) {
200 handleException("Failed to write log", e);
201 }
202 }
203 }
204
Andreas Gampea36dc622018-02-05 17:19:22 -0800205 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700206 private void openLogLocked(long now) {
207 // If we already have a log file opened and the date has't changed, just use it.
208 final long day = now % DateUtils.DAY_IN_MILLIS;
209 if ((mLogWriter != null) && (day == mCurrentLogFileDayTimestamp)) {
210 return;
211 }
212
213 // Otherwise create a new log file.
214 closeCurrentLogLocked();
215
216 mCurrentLogFileDayTimestamp = day;
217
218 mCachedDate.setTime(now);
219 final String filename = "synclog-" + sFilenameDateFormat.format(mCachedDate) + ".log";
220 final File file = new File(mLogPath, filename);
221
222 file.getParentFile().mkdirs();
223
224 try {
225 mLogWriter = new FileWriter(file, /* append= */ true);
226 } catch (IOException e) {
227 handleException("Failed to open log file: " + file, e);
228 }
229 }
230
Andreas Gampea36dc622018-02-05 17:19:22 -0800231 @GuardedBy("mLock")
Makoto Onukia9dca242017-06-21 17:06:49 -0700232 private void closeCurrentLogLocked() {
233 IoUtils.closeQuietly(mLogWriter);
234 mLogWriter = null;
235 }
236
237 @Override
238 public void purgeOldLogs() {
239 synchronized (mLock) {
240 FileUtils.deleteOlderFiles(mLogPath, /* keepCount= */ 1, mKeepAgeMs);
241 }
242 }
243
244 @Override
245 public String jobParametersToString(JobParameters params) {
Makoto Onuki5397be12017-12-20 16:22:34 +0900246 return SyncJobService.jobParametersToString(params);
Makoto Onukia9dca242017-06-21 17:06:49 -0700247 }
248
249 @Override
250 public void dumpAll(PrintWriter pw) {
251 synchronized (mLock) {
252 final String[] files = mLogPath.list();
253 if (files == null || (files.length == 0)) {
254 return;
255 }
256 Arrays.sort(files);
257
258 for (String file : files) {
259 dumpFile(pw, new File(mLogPath, file));
260 }
261 }
262 }
263
264 private void dumpFile(PrintWriter pw, File file) {
265 Slog.w(TAG, "Dumping " + file);
266 final char[] buffer = new char[32 * 1024];
267
268 try (Reader in = new BufferedReader(new FileReader(file))) {
269 int read;
270 while ((read = in.read(buffer)) >= 0) {
271 if (read > 0) {
272 pw.write(buffer, 0, read);
273 }
274 }
275 } catch (IOException e) {
276 }
277 }
278 }
Makoto Onuki5fc60022019-01-15 09:34:05 -0800279
280 static String logSafe(Account account) {
281 return account == null ? "[null]" : "***/" + account.type;
282 }
283
284 static String logSafe(EndPoint endPoint) {
285 return endPoint == null ? "[null]" : endPoint.toSafeString();
286 }
287
288 static String logSafe(SyncOperation operation) {
289 return operation == null ? "[null]" : operation.toSafeString();
290 }
291
292 static String logSafe(ActiveSyncContext asc) {
293 return asc == null ? "[null]" : asc.toSafeString();
294 }
Makoto Onukia9dca242017-06-21 17:06:49 -0700295}