blob: db794643b4df2b2dc63a6630f62de224fe5b2914 [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;
25import android.util.Slog;
26
27import com.android.internal.annotations.GuardedBy;
28
29import libcore.io.IoUtils;
30
31import java.io.BufferedReader;
32import java.io.File;
33import java.io.FileReader;
34import java.io.FileWriter;
35import java.io.IOException;
36import java.io.PrintWriter;
37import java.io.Reader;
38import java.io.Writer;
39import java.text.SimpleDateFormat;
40import java.util.Arrays;
41import java.util.Date;
42import java.util.concurrent.TimeUnit;
43
44/**
45 * Implements a rotating file logger for the sync manager, which is enabled only on userdebug/eng
46 * builds (unless debug.synclog is set to 1).
47 *
48 * Note this class could be used for other purposes too, but in general we don't want various
49 * system components to log to files, so it's put in a local package here.
50 */
51public class SyncLogger {
52 private static final String TAG = "SyncLogger";
53
54 private static SyncLogger sInstance;
55
56 SyncLogger() {
57 }
58
59 /**
60 * @return the singleton instance.
61 */
62 public static synchronized SyncLogger getInstance() {
63 if (sInstance == null) {
64 final boolean enable = "1".equals(SystemProperties.get("debug.synclog",
65 Build.IS_DEBUGGABLE ? "1" : "0"));
66 if (enable) {
67 sInstance = new RotatingFileLogger();
68 } else {
69 sInstance = new SyncLogger();
70 }
71 }
72 return sInstance;
73 }
74
75 /**
76 * Write strings to the log file.
77 */
78 public void log(Object... message) {
79 }
80
81 /**
82 * Remove old log files.
83 */
84 public void purgeOldLogs() {
85 // The default implementation is no-op.
86 }
87
88 public String jobParametersToString(JobParameters params) {
89 // The default implementation is no-op.
90 return "";
91 }
92
93 /**
94 * Dump all existing log files into a given writer.
95 */
96 public void dumpAll(PrintWriter pw) {
97 }
98
99 /**
100 * Actual implementation which is only used on userdebug/eng builds (by default).
101 */
102 private static class RotatingFileLogger extends SyncLogger {
103 private final Object mLock = new Object();
104
105 private final long mKeepAgeMs = TimeUnit.DAYS.toMillis(7);
106
107 private static final SimpleDateFormat sTimestampFormat
108 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
109
110 private static final SimpleDateFormat sFilenameDateFormat
111 = new SimpleDateFormat("yyyy-MM-dd");
112
113 @GuardedBy("mLock")
114 private final Date mCachedDate = new Date();
115
116 @GuardedBy("mLock")
117 private final StringBuilder mStringBuilder = new StringBuilder();
118
119 private final File mLogPath;
120
121 @GuardedBy("mLock")
122 private long mCurrentLogFileDayTimestamp;
123
124 @GuardedBy("mLock")
125 private Writer mLogWriter;
126
127 @GuardedBy("mLock")
128 private boolean mErrorShown;
129
130 RotatingFileLogger() {
131 mLogPath = new File(Environment.getDataSystemDirectory(), "syncmanager-log");
132 }
133
134 private void handleException(String message, Exception e) {
135 if (!mErrorShown) {
136 Slog.e(TAG, message, e);
137 mErrorShown = true;
138 }
139 }
140
141 @Override
142 public void log(Object... message) {
143 if (message == null) {
144 return;
145 }
146 synchronized (mLock) {
147 final long now = System.currentTimeMillis();
148 openLogLocked(now);
149 if (mLogWriter == null) {
150 return; // Couldn't open log file?
151 }
152
153 mStringBuilder.setLength(0);
154 mCachedDate.setTime(now);
155 mStringBuilder.append(sTimestampFormat.format(mCachedDate));
156 mStringBuilder.append(' ');
157
158 mStringBuilder.append(android.os.Process.myTid());
159 mStringBuilder.append(' ');
160
161 for (Object o : message) {
162 mStringBuilder.append(o);
163 }
164 mStringBuilder.append('\n');
165
166 try {
167 mLogWriter.append(mStringBuilder);
168 mLogWriter.flush();
169 } catch (IOException e) {
170 handleException("Failed to write log", e);
171 }
172 }
173 }
174
175 private void openLogLocked(long now) {
176 // If we already have a log file opened and the date has't changed, just use it.
177 final long day = now % DateUtils.DAY_IN_MILLIS;
178 if ((mLogWriter != null) && (day == mCurrentLogFileDayTimestamp)) {
179 return;
180 }
181
182 // Otherwise create a new log file.
183 closeCurrentLogLocked();
184
185 mCurrentLogFileDayTimestamp = day;
186
187 mCachedDate.setTime(now);
188 final String filename = "synclog-" + sFilenameDateFormat.format(mCachedDate) + ".log";
189 final File file = new File(mLogPath, filename);
190
191 file.getParentFile().mkdirs();
192
193 try {
194 mLogWriter = new FileWriter(file, /* append= */ true);
195 } catch (IOException e) {
196 handleException("Failed to open log file: " + file, e);
197 }
198 }
199
200 private void closeCurrentLogLocked() {
201 IoUtils.closeQuietly(mLogWriter);
202 mLogWriter = null;
203 }
204
205 @Override
206 public void purgeOldLogs() {
207 synchronized (mLock) {
208 FileUtils.deleteOlderFiles(mLogPath, /* keepCount= */ 1, mKeepAgeMs);
209 }
210 }
211
212 @Override
213 public String jobParametersToString(JobParameters params) {
214 if (params == null) {
215 return "job:null";
216 } else {
217 return "job:#" + params.getJobId() + ":"
218 + SyncOperation.maybeCreateFromJobExtras(params.getExtras());
219 }
220 }
221
222 @Override
223 public void dumpAll(PrintWriter pw) {
224 synchronized (mLock) {
225 final String[] files = mLogPath.list();
226 if (files == null || (files.length == 0)) {
227 return;
228 }
229 Arrays.sort(files);
230
231 for (String file : files) {
232 dumpFile(pw, new File(mLogPath, file));
233 }
234 }
235 }
236
237 private void dumpFile(PrintWriter pw, File file) {
238 Slog.w(TAG, "Dumping " + file);
239 final char[] buffer = new char[32 * 1024];
240
241 try (Reader in = new BufferedReader(new FileReader(file))) {
242 int read;
243 while ((read = in.read(buffer)) >= 0) {
244 if (read > 0) {
245 pw.write(buffer, 0, read);
246 }
247 }
248 } catch (IOException e) {
249 }
250 }
251 }
252}