/*
|
* Copyright (C) 2017 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License
|
*/
|
|
package com.android.server.content;
|
|
import android.accounts.Account;
|
import android.app.job.JobParameters;
|
import android.os.Build;
|
import android.os.Environment;
|
import android.os.FileUtils;
|
import android.os.Handler;
|
import android.os.Looper;
|
import android.os.Message;
|
import android.os.SystemProperties;
|
import android.text.format.DateUtils;
|
import android.util.Log;
|
import android.util.Slog;
|
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.util.IntPair;
|
import com.android.server.IoThread;
|
import com.android.server.content.SyncManager.ActiveSyncContext;
|
import com.android.server.content.SyncStorageEngine.EndPoint;
|
|
import libcore.io.IoUtils;
|
|
import java.io.BufferedReader;
|
import java.io.File;
|
import java.io.FileReader;
|
import java.io.FileWriter;
|
import java.io.IOException;
|
import java.io.PrintWriter;
|
import java.io.Reader;
|
import java.io.Writer;
|
import java.text.SimpleDateFormat;
|
import java.util.Arrays;
|
import java.util.Date;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* Implements a rotating file logger for the sync manager, which is enabled only on userdebug/eng
|
* builds (unless debug.synclog is set to 1).
|
*
|
* Note this class could be used for other purposes too, but in general we don't want various
|
* system components to log to files, so it's put in a local package here.
|
*/
|
public class SyncLogger {
|
private static final String TAG = "SyncLogger";
|
|
private static SyncLogger sInstance;
|
|
// Special UID used for logging to denote the self process.
|
public static final int CALLING_UID_SELF = -1;
|
|
SyncLogger() {
|
}
|
|
/**
|
* @return the singleton instance.
|
*/
|
public static synchronized SyncLogger getInstance() {
|
if (sInstance == null) {
|
final String flag = SystemProperties.get("debug.synclog");
|
final boolean enable =
|
(Build.IS_DEBUGGABLE
|
|| "1".equals(flag)
|
|| Log.isLoggable(TAG, Log.VERBOSE)) && !"0".equals(flag);
|
if (enable) {
|
sInstance = new RotatingFileLogger();
|
} else {
|
sInstance = new SyncLogger();
|
}
|
}
|
return sInstance;
|
}
|
|
/**
|
* Write strings to the log file.
|
*/
|
public void log(Object... message) {
|
}
|
|
/**
|
* Remove old log files.
|
*/
|
public void purgeOldLogs() {
|
// The default implementation is no-op.
|
}
|
|
public String jobParametersToString(JobParameters params) {
|
// The default implementation is no-op.
|
return "";
|
}
|
|
/**
|
* Dump all existing log files into a given writer.
|
*/
|
public void dumpAll(PrintWriter pw) {
|
}
|
|
/**
|
* @return whether log is enabled or not.
|
*/
|
public boolean enabled() {
|
return false;
|
}
|
|
/**
|
* Actual implementation which is only used on userdebug/eng builds (by default).
|
*/
|
private static class RotatingFileLogger extends SyncLogger {
|
private final Object mLock = new Object();
|
|
private final long mKeepAgeMs = TimeUnit.DAYS.toMillis(7);
|
|
private static final SimpleDateFormat sTimestampFormat
|
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
|
private static final SimpleDateFormat sFilenameDateFormat
|
= new SimpleDateFormat("yyyy-MM-dd");
|
|
@GuardedBy("mLock")
|
private final Date mCachedDate = new Date();
|
|
@GuardedBy("mLock")
|
private final StringBuilder mStringBuilder = new StringBuilder();
|
|
private final File mLogPath;
|
|
@GuardedBy("mLock")
|
private long mCurrentLogFileDayTimestamp;
|
|
@GuardedBy("mLock")
|
private Writer mLogWriter;
|
|
@GuardedBy("mLock")
|
private boolean mErrorShown;
|
|
private static final boolean DO_LOGCAT = Log.isLoggable(TAG, Log.DEBUG);
|
|
private final MyHandler mHandler;
|
|
RotatingFileLogger() {
|
mLogPath = new File(Environment.getDataSystemDirectory(), "syncmanager-log");
|
mHandler = new MyHandler(IoThread.get().getLooper());
|
}
|
|
@Override
|
public boolean enabled() {
|
return true;
|
}
|
|
private void handleException(String message, Exception e) {
|
if (!mErrorShown) {
|
Slog.e(TAG, message, e);
|
mErrorShown = true;
|
}
|
}
|
|
@Override
|
public void log(Object... message) {
|
if (message == null) {
|
return;
|
}
|
final long now = System.currentTimeMillis();
|
mHandler.log(now, message);
|
}
|
|
void logInner(long now, Object[] message) {
|
synchronized (mLock) {
|
openLogLocked(now);
|
if (mLogWriter == null) {
|
return; // Couldn't open log file?
|
}
|
|
mStringBuilder.setLength(0);
|
mCachedDate.setTime(now);
|
mStringBuilder.append(sTimestampFormat.format(mCachedDate));
|
mStringBuilder.append(' ');
|
|
mStringBuilder.append(android.os.Process.myTid());
|
mStringBuilder.append(' ');
|
|
final int messageStart = mStringBuilder.length();
|
|
for (Object o : message) {
|
mStringBuilder.append(o);
|
}
|
mStringBuilder.append('\n');
|
|
try {
|
mLogWriter.append(mStringBuilder);
|
mLogWriter.flush();
|
|
// Also write on logcat.
|
if (DO_LOGCAT) {
|
Log.d(TAG, mStringBuilder.substring(messageStart));
|
}
|
} catch (IOException e) {
|
handleException("Failed to write log", e);
|
}
|
}
|
}
|
|
@GuardedBy("mLock")
|
private void openLogLocked(long now) {
|
// If we already have a log file opened and the date has't changed, just use it.
|
final long day = now % DateUtils.DAY_IN_MILLIS;
|
if ((mLogWriter != null) && (day == mCurrentLogFileDayTimestamp)) {
|
return;
|
}
|
|
// Otherwise create a new log file.
|
closeCurrentLogLocked();
|
|
mCurrentLogFileDayTimestamp = day;
|
|
mCachedDate.setTime(now);
|
final String filename = "synclog-" + sFilenameDateFormat.format(mCachedDate) + ".log";
|
final File file = new File(mLogPath, filename);
|
|
file.getParentFile().mkdirs();
|
|
try {
|
mLogWriter = new FileWriter(file, /* append= */ true);
|
} catch (IOException e) {
|
handleException("Failed to open log file: " + file, e);
|
}
|
}
|
|
@GuardedBy("mLock")
|
private void closeCurrentLogLocked() {
|
IoUtils.closeQuietly(mLogWriter);
|
mLogWriter = null;
|
}
|
|
@Override
|
public void purgeOldLogs() {
|
synchronized (mLock) {
|
FileUtils.deleteOlderFiles(mLogPath, /* keepCount= */ 1, mKeepAgeMs);
|
}
|
}
|
|
@Override
|
public String jobParametersToString(JobParameters params) {
|
return SyncJobService.jobParametersToString(params);
|
}
|
|
@Override
|
public void dumpAll(PrintWriter pw) {
|
synchronized (mLock) {
|
final String[] files = mLogPath.list();
|
if (files == null || (files.length == 0)) {
|
return;
|
}
|
Arrays.sort(files);
|
|
for (String file : files) {
|
dumpFile(pw, new File(mLogPath, file));
|
}
|
}
|
}
|
|
private void dumpFile(PrintWriter pw, File file) {
|
Slog.w(TAG, "Dumping " + file);
|
final char[] buffer = new char[32 * 1024];
|
|
try (Reader in = new BufferedReader(new FileReader(file))) {
|
int read;
|
while ((read = in.read(buffer)) >= 0) {
|
if (read > 0) {
|
pw.write(buffer, 0, read);
|
}
|
}
|
} catch (IOException e) {
|
}
|
}
|
|
private class MyHandler extends Handler {
|
public static final int MSG_LOG_ID = 1;
|
|
MyHandler(Looper looper) {
|
super(looper);
|
}
|
|
public void log(long now, Object[] message) {
|
obtainMessage(MSG_LOG_ID, IntPair.first(now), IntPair.second(now), message)
|
.sendToTarget();
|
}
|
|
@Override
|
public void handleMessage(Message msg) {
|
switch (msg.what) {
|
case MSG_LOG_ID: {
|
logInner(IntPair.of(msg.arg1, msg.arg2), (Object[]) msg.obj);
|
break;
|
}
|
}
|
}
|
}
|
}
|
|
static String logSafe(Account account) {
|
return account == null ? "[null]" : account.toSafeString();
|
}
|
|
static String logSafe(EndPoint endPoint) {
|
return endPoint == null ? "[null]" : endPoint.toSafeString();
|
}
|
|
static String logSafe(SyncOperation operation) {
|
return operation == null ? "[null]" : operation.toSafeString();
|
}
|
|
static String logSafe(ActiveSyncContext asc) {
|
return asc == null ? "[null]" : asc.toSafeString();
|
}
|
}
|