blob: 4e15668f6a3408ec0bd10baf35f5977b704458d0 [file] [log] [blame]
/*
* Copyright (C) 2019 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.systemui.log;
import android.os.Build;
import android.os.SystemProperties;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.Locale;
/**
* Thread-safe logger in SystemUI which prints logs to logcat and stores logs to be
* printed by the DumpController. This is an alternative to printing directly
* to avoid logs being deleted by chatty. The number of logs retained is varied based on
* whether the build is {@link Build.IS_DEBUGGABLE}.
*
* To manually view the logs via adb:
* adb shell dumpsys activity service com.android.systemui/.SystemUIService \
* dependency DumpController <SysuiLogId>
*
* Logs can be disabled by setting the following SystemProperty and then restarting the device:
* adb shell setprop persist.sysui.log.enabled.<id> true/false && adb reboot
*
* @param <E> Type of event we'll be logging
*/
public class SysuiLog<E extends Event> implements Dumpable {
public static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
protected final Object mDataLock = new Object();
private final String mId;
private final int mMaxLogs;
protected boolean mEnabled;
protected boolean mLogToLogcatEnabled;
@VisibleForTesting protected ArrayDeque<E> mTimeline;
/**
* Creates a SysuiLog
* @param dumpController where to register this logger's dumpsys
* @param id user-readable tag for this logger
* @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true
* @param maxLogs maximum number of logs to retain when {@link sDebuggable} is false
*/
public SysuiLog(DumpController dumpController, String id, int maxDebugLogs, int maxLogs) {
this(dumpController, id, sDebuggable ? maxDebugLogs : maxLogs,
SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED),
SystemProperties.getBoolean(SYSPROP_LOGCAT_ENABLED_PREFIX + id,
DEFAULT_LOGCAT_ENABLED));
}
@VisibleForTesting
protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled,
boolean logcatEnabled) {
mId = id;
mMaxLogs = maxLogs;
mEnabled = enabled;
mLogToLogcatEnabled = logcatEnabled;
mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null;
dumpController.registerDumpable(mId, this);
}
/**
* Logs an event to the timeline which can be printed by the dumpsys.
* May also log to logcat if enabled.
* @return the last event that was discarded from the Timeline (can be recycled)
*/
public E log(E event) {
if (!mEnabled) {
return null;
}
E recycledEvent = null;
synchronized (mDataLock) {
if (mTimeline.size() >= mMaxLogs) {
recycledEvent = mTimeline.removeFirst();
}
mTimeline.add(event);
}
if (mLogToLogcatEnabled) {
final String strEvent = eventToString(event);
switch (event.getLogLevel()) {
case Event.VERBOSE:
Log.v(mId, strEvent);
break;
case Event.DEBUG:
Log.d(mId, strEvent);
break;
case Event.ERROR:
Log.e(mId, strEvent);
break;
case Event.INFO:
Log.i(mId, strEvent);
break;
case Event.WARN:
Log.w(mId, strEvent);
break;
}
}
if (recycledEvent != null) {
recycledEvent.recycle();
}
return recycledEvent;
}
/**
* @return user-readable string of the given event with timestamp
*/
private String eventToTimestampedString(Event event) {
StringBuilder sb = new StringBuilder();
sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp()));
sb.append(" ");
sb.append(event.getMessage());
return sb.toString();
}
/**
* @return user-readable string of the given event without a timestamp
*/
public String eventToString(Event event) {
return event.getMessage();
}
@GuardedBy("mDataLock")
private void dumpTimelineLocked(PrintWriter pw) {
pw.println("\tTimeline:");
for (Event event : mTimeline) {
pw.println("\t" + eventToTimestampedString(event));
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(mId + ":");
if (mEnabled) {
synchronized (mDataLock) {
dumpTimelineLocked(pw);
}
} else {
pw.print(" - Logging disabled.");
}
}
private static boolean sDebuggable = Build.IS_DEBUGGABLE;
private static final String SYSPROP_ENABLED_PREFIX = "persist.sysui.log.enabled.";
private static final String SYSPROP_LOGCAT_ENABLED_PREFIX = "persist.sysui.log.enabled.logcat.";
private static final boolean DEFAULT_ENABLED = sDebuggable;
private static final boolean DEFAULT_LOGCAT_ENABLED = false;
private static final int DEFAULT_MAX_DEBUG_LOGS = 100;
private static final int DEFAULT_MAX_LOGS = 50;
}