294 lines
11 KiB
Java
294 lines
11 KiB
Java
/*
|
|
* Copyright (C) 2015 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.messaging.util;
|
|
|
|
import android.os.Process;
|
|
import android.util.Log;
|
|
|
|
import com.android.messaging.Factory;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.logging.FileHandler;
|
|
import java.util.logging.Formatter;
|
|
import java.util.logging.Handler;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
* Save the app's own log to dump along with adb bugreport
|
|
*/
|
|
public abstract class LogSaver {
|
|
/**
|
|
* Writes the accumulated log entries, from oldest to newest, to the specified PrintWriter.
|
|
* Log lines are emitted in much the same form as logcat -v threadtime -- specifically,
|
|
* lines will include a timestamp, pid, tid, level, and tag.
|
|
*
|
|
* @param writer The PrintWriter to output
|
|
*/
|
|
public abstract void dump(PrintWriter writer);
|
|
|
|
/**
|
|
* Log a line
|
|
*
|
|
* @param level The log level to use
|
|
* @param tag The log tag
|
|
* @param msg The message of the log line
|
|
*/
|
|
public abstract void log(int level, String tag, String msg);
|
|
|
|
/**
|
|
* Check if the LogSaver still matches the current Gservices settings
|
|
*
|
|
* @return true if matches, false otherwise
|
|
*/
|
|
public abstract boolean isCurrent();
|
|
|
|
private LogSaver() {
|
|
}
|
|
|
|
public static LogSaver newInstance() {
|
|
final boolean persistent = BugleGservices.get().getBoolean(
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER,
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
|
|
if (persistent) {
|
|
final int setSize = BugleGservices.get().getInt(
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
|
|
final int fileLimitBytes = BugleGservices.get().getInt(
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
|
|
return new DiskLogSaver(setSize, fileLimitBytes);
|
|
} else {
|
|
final int size = BugleGservices.get().getInt(
|
|
BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
|
|
BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
|
|
return new MemoryLogSaver(size);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A circular in-memory log to be used to log potentially verbose logs. The logs will be
|
|
* persisted in memory in the application and can be dumped by various dump() methods.
|
|
* For example, adb shell dumpsys activity provider com.android.messaging.
|
|
* The dump will also show up in bugreports.
|
|
*/
|
|
private static final class MemoryLogSaver extends LogSaver {
|
|
/**
|
|
* Record to store a single log entry. Stores timestamp, tid, level, tag, and message.
|
|
* It can be reused when the circular log rolls over. This avoids creating new objects.
|
|
*/
|
|
private static class LogRecord {
|
|
int mTid;
|
|
String mLevelString;
|
|
long mTimeMillis; // from System.currentTimeMillis
|
|
String mTag;
|
|
String mMessage;
|
|
|
|
LogRecord() {
|
|
}
|
|
|
|
void set(int tid, int level, long time, String tag, String message) {
|
|
this.mTid = tid;
|
|
this.mTimeMillis = time;
|
|
this.mTag = tag;
|
|
this.mMessage = message;
|
|
this.mLevelString = getLevelString(level);
|
|
}
|
|
}
|
|
|
|
private final int mSize;
|
|
private final CircularArray<LogRecord> mLogList;
|
|
private final Object mLock;
|
|
|
|
private final SimpleDateFormat mSdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
|
|
|
|
public MemoryLogSaver(final int size) {
|
|
mSize = size;
|
|
mLogList = new CircularArray<LogRecord>(size);
|
|
mLock = new Object();
|
|
}
|
|
|
|
@Override
|
|
public void dump(PrintWriter writer) {
|
|
int pid = Process.myPid();
|
|
synchronized (mLock) {
|
|
for (int i = 0; i < mLogList.count(); i++) {
|
|
LogRecord rec = mLogList.get(i);
|
|
writer.println(String.format("%s %5d %5d %s %s: %s",
|
|
mSdf.format(rec.mTimeMillis),
|
|
pid, rec.mTid, rec.mLevelString, rec.mTag, rec.mMessage));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void log(int level, String tag, String msg) {
|
|
synchronized (mLock) {
|
|
LogRecord rec = mLogList.getFree();
|
|
if (rec == null) {
|
|
rec = new LogRecord();
|
|
}
|
|
rec.set(Process.myTid(), level, System.currentTimeMillis(), tag, msg);
|
|
mLogList.add(rec);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isCurrent() {
|
|
final boolean persistent = BugleGservices.get().getBoolean(
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER,
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
|
|
if (persistent) {
|
|
return false;
|
|
}
|
|
final int size = BugleGservices.get().getInt(
|
|
BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
|
|
BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
|
|
return size == mSize;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A persistent, on-disk log saver. It uses the standard Java util logger along with
|
|
* a rotation log file set to store the logs in app's local file directory "app_logs".
|
|
*/
|
|
private static final class DiskLogSaver extends LogSaver {
|
|
private static final String DISK_LOG_DIR_NAME = "logs";
|
|
|
|
private final int mSetSize;
|
|
private final int mFileLimitBytes;
|
|
private Logger mDiskLogger;
|
|
|
|
public DiskLogSaver(final int setSize, final int fileLimitBytes) {
|
|
Assert.isTrue(setSize > 0);
|
|
Assert.isTrue(fileLimitBytes > 0);
|
|
mSetSize = setSize;
|
|
mFileLimitBytes = fileLimitBytes;
|
|
initDiskLog();
|
|
}
|
|
|
|
private static void clearDefaultHandlers(Logger logger) {
|
|
Assert.notNull(logger);
|
|
for (Handler handler : logger.getHandlers()) {
|
|
logger.removeHandler(handler);
|
|
}
|
|
}
|
|
|
|
private void initDiskLog() {
|
|
mDiskLogger = Logger.getLogger(LogUtil.BUGLE_TAG);
|
|
// We don't want the default console handler
|
|
clearDefaultHandlers(mDiskLogger);
|
|
// Don't want duplicate print in system log
|
|
mDiskLogger.setUseParentHandlers(false);
|
|
// FileHandler manages the log files in a fixed rotation set
|
|
final File logDir = Factory.get().getApplicationContext().getDir(
|
|
DISK_LOG_DIR_NAME, 0/*mode*/);
|
|
FileHandler handler = null;
|
|
try {
|
|
handler = new FileHandler(
|
|
logDir + "/%g.log", mFileLimitBytes, mSetSize, true/*append*/);
|
|
} catch (Exception e) {
|
|
Log.e(LogUtil.BUGLE_TAG, "LogSaver: fail to init disk logger", e);
|
|
return;
|
|
}
|
|
final Formatter formatter = new Formatter() {
|
|
@Override
|
|
public String format(java.util.logging.LogRecord r) {
|
|
return r.getMessage();
|
|
}
|
|
};
|
|
handler.setFormatter(formatter);
|
|
handler.setLevel(Level.ALL);
|
|
mDiskLogger.addHandler(handler);
|
|
}
|
|
|
|
@Override
|
|
public void dump(PrintWriter writer) {
|
|
for (int i = mSetSize - 1; i >= 0; i--) {
|
|
final File logDir = Factory.get().getApplicationContext().getDir(
|
|
DISK_LOG_DIR_NAME, 0/*mode*/);
|
|
final String logFilePath = logDir + "/" + i + ".log";
|
|
try {
|
|
final File logFile = new File(logFilePath);
|
|
if (!logFile.exists()) {
|
|
continue;
|
|
}
|
|
final BufferedReader reader = new BufferedReader(new FileReader(logFile));
|
|
for (String line; (line = reader.readLine()) != null;) {
|
|
line = line.trim();
|
|
writer.println(line);
|
|
}
|
|
} catch (FileNotFoundException e) {
|
|
Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not find log file " + logFilePath);
|
|
} catch (IOException e) {
|
|
Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not read log file", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void log(int level, String tag, String msg) {
|
|
final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
|
|
mDiskLogger.info(String.format("%s %5d %5d %s %s: %s\n",
|
|
sdf.format(System.currentTimeMillis()),
|
|
Process.myPid(), Process.myTid(), getLevelString(level), tag, msg));
|
|
}
|
|
|
|
@Override
|
|
public boolean isCurrent() {
|
|
final boolean persistent = BugleGservices.get().getBoolean(
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER,
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
|
|
if (!persistent) {
|
|
return false;
|
|
}
|
|
final int setSize = BugleGservices.get().getInt(
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
|
|
final int fileLimitBytes = BugleGservices.get().getInt(
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
|
|
BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
|
|
return setSize == mSetSize && fileLimitBytes == mFileLimitBytes;
|
|
}
|
|
}
|
|
|
|
private static String getLevelString(final int level) {
|
|
switch (level) {
|
|
case android.util.Log.DEBUG:
|
|
return "D";
|
|
case android.util.Log.WARN:
|
|
return "W";
|
|
case android.util.Log.INFO:
|
|
return "I";
|
|
case android.util.Log.VERBOSE:
|
|
return "V";
|
|
case android.util.Log.ERROR:
|
|
return "E";
|
|
case android.util.Log.ASSERT:
|
|
return "A";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
}
|