New SysUi logger
Test: atest SysuiLogTest Test: atest RichEventTest Bug: 141470043 Change-Id: Ied8ccc58734490230039c851cd3f020a048642d8
This commit is contained in:
67
packages/SystemUI/src/com/android/systemui/log/Event.java
Normal file
67
packages/SystemUI/src/com/android/systemui/log/Event.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Stores information about an event that occurred in SystemUI to be used for debugging and triage.
|
||||
* Every event has a time stamp, log level and message.
|
||||
* Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
|
||||
*/
|
||||
public class Event {
|
||||
public static final int UNINITIALIZED = -1;
|
||||
|
||||
@IntDef({ERROR, WARN, INFO, DEBUG, VERBOSE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Level {}
|
||||
public static final int VERBOSE = 2;
|
||||
public static final int DEBUG = 3;
|
||||
public static final int INFO = 4;
|
||||
public static final int WARN = 5;
|
||||
public static final int ERROR = 6;
|
||||
|
||||
private long mTimestamp;
|
||||
private @Level int mLogLevel = DEBUG;
|
||||
protected String mMessage;
|
||||
|
||||
public Event(String message) {
|
||||
mTimestamp = System.currentTimeMillis();
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public Event(@Level int logLevel, String message) {
|
||||
mTimestamp = System.currentTimeMillis();
|
||||
mLogLevel = logLevel;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
public @Level int getLogLevel() {
|
||||
return mLogLevel;
|
||||
}
|
||||
}
|
||||
107
packages/SystemUI/src/com/android/systemui/log/RichEvent.java
Normal file
107
packages/SystemUI/src/com/android/systemui/log/RichEvent.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Stores information about an event that occurred in SystemUI to be used for debugging and triage.
|
||||
* Every rich event has a time stamp, event type, and log level, with the option to provide the
|
||||
* reason this event was triggered.
|
||||
* Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
|
||||
*/
|
||||
public abstract class RichEvent extends Event {
|
||||
private final int mType;
|
||||
private final String mReason;
|
||||
|
||||
/**
|
||||
* Create a rich event that includes an event type that matches with an index in the array
|
||||
* getEventLabels().
|
||||
*/
|
||||
public RichEvent(@Event.Level int logLevel, int type, String reason) {
|
||||
super(logLevel, null);
|
||||
final int numEvents = getEventLabels().length;
|
||||
if (type < 0 || type >= numEvents) {
|
||||
throw new IllegalArgumentException("Unsupported event type. Events only supported"
|
||||
+ " from 0 to " + (numEvents - 1) + ", but given type=" + type);
|
||||
}
|
||||
mType = type;
|
||||
mReason = reason;
|
||||
mMessage = getEventLabels()[mType] + " " + mReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the event labels. The index represents the event type and the
|
||||
* corresponding String stored at that index is the user-readable representation of that event.
|
||||
* @return array of user readable events, where the index represents its event type constant
|
||||
*/
|
||||
public abstract String[] getEventLabels();
|
||||
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return mReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder to build a RichEvent.
|
||||
* @param <B> Log specific builder that is extending this builder
|
||||
*/
|
||||
public abstract static class Builder<B extends Builder<B>> {
|
||||
public static final int UNINITIALIZED = -1;
|
||||
|
||||
private B mBuilder = getBuilder();
|
||||
protected int mType = UNINITIALIZED;
|
||||
protected String mReason;
|
||||
protected @Level int mLogLevel;
|
||||
|
||||
/**
|
||||
* Get the log-specific builder.
|
||||
*/
|
||||
public abstract B getBuilder();
|
||||
|
||||
/**
|
||||
* Build the log-specific event.
|
||||
*/
|
||||
public abstract RichEvent build();
|
||||
|
||||
/**
|
||||
* Optional - set the log level. Defaults to DEBUG.
|
||||
*/
|
||||
public B setLogLevel(@Level int logLevel) {
|
||||
mLogLevel = logLevel;
|
||||
return mBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required - set the event type. These events must correspond with the events from
|
||||
* getEventLabels().
|
||||
*/
|
||||
public B setType(int type) {
|
||||
mType = type;
|
||||
return mBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional - set the reason why this event was triggered.
|
||||
*/
|
||||
public B setReason(String reason) {
|
||||
mReason = reason;
|
||||
return mBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
163
packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
Normal file
163
packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.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>
|
||||
*/
|
||||
public class SysuiLog implements Dumpable {
|
||||
public static final SimpleDateFormat DATE_FORMAT =
|
||||
new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
|
||||
|
||||
private final Object mDataLock = new Object();
|
||||
private final String mId;
|
||||
private final int mMaxLogs;
|
||||
private boolean mEnabled;
|
||||
|
||||
@VisibleForTesting protected ArrayDeque<Event> mTimeline;
|
||||
|
||||
/**
|
||||
* Creates a SysuiLog
|
||||
* To enable or disable logs, set the system property and then restart the device:
|
||||
* adb shell setprop sysui.log.enabled.<id> true/false && adb reboot
|
||||
* @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));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled) {
|
||||
mId = id;
|
||||
mMaxLogs = maxLogs;
|
||||
mEnabled = enabled;
|
||||
mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null;
|
||||
dumpController.registerDumpable(mId, this);
|
||||
}
|
||||
|
||||
public SysuiLog(DumpController dumpController, String id) {
|
||||
this(dumpController, id, DEFAULT_MAX_DEBUG_LOGS, DEFAULT_MAX_LOGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an event to the timeline which can be printed by the dumpsys.
|
||||
* May also log to logcat if enabled.
|
||||
* @return true if event was logged, else false
|
||||
*/
|
||||
public boolean log(Event event) {
|
||||
if (!mEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized (mDataLock) {
|
||||
if (mTimeline.size() >= mMaxLogs) {
|
||||
mTimeline.removeFirst();
|
||||
}
|
||||
|
||||
mTimeline.add(event);
|
||||
}
|
||||
|
||||
if (LOG_TO_LOGCAT_ENABLED) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return user-readable string of the given event
|
||||
*/
|
||||
public String eventToString(Event event) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp()));
|
||||
sb.append(" ");
|
||||
sb.append(event.getMessage());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* only call on this method if you have the mDataLock
|
||||
*/
|
||||
private void dumpTimelineLocked(PrintWriter pw) {
|
||||
pw.println("\tTimeline:");
|
||||
|
||||
for (Event event : mTimeline) {
|
||||
pw.println("\t" + eventToString(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 = "sysui.log.enabled.";
|
||||
private static final boolean LOG_TO_LOGCAT_ENABLED = sDebuggable;
|
||||
private static final boolean DEFAULT_ENABLED = sDebuggable;
|
||||
private static final int DEFAULT_MAX_DEBUG_LOGS = 100;
|
||||
private static final int DEFAULT_MAX_LOGS = 50;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 static junit.framework.Assert.assertEquals;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class RichEventTest extends SysuiTestCase {
|
||||
|
||||
private static final int TOTAL_EVENT_TYPES = 1;
|
||||
|
||||
@Test
|
||||
public void testCreateRichEvent_invalidType() {
|
||||
try {
|
||||
// indexing for events starts at 0, so TOTAL_EVENT_TYPES is an invalid type
|
||||
new TestableRichEvent(Event.DEBUG, TOTAL_EVENT_TYPES, "msg");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.fail("Expected an invalidArgumentException since the event type was invalid.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateRichEvent() {
|
||||
final int eventType = 0;
|
||||
RichEvent e = new TestableRichEvent(Event.DEBUG, eventType, "msg");
|
||||
assertEquals(e.getType(), eventType);
|
||||
}
|
||||
|
||||
class TestableRichEvent extends RichEvent {
|
||||
TestableRichEvent(int logLevel, int type, String reason) {
|
||||
super(logLevel, type, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEventLabels() {
|
||||
return new String[]{"ACTION_NAME"};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 static junit.framework.Assert.assertEquals;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.DumpController;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class SysuiLogTest extends SysuiTestCase {
|
||||
private static final String TEST_ID = "TestLogger";
|
||||
private static final int MAX_LOGS = 5;
|
||||
|
||||
@Mock
|
||||
private DumpController mDumpController;
|
||||
private SysuiLog mSysuiLog;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogDisabled_noLogsWritten() {
|
||||
mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, false);
|
||||
assertEquals(mSysuiLog.mTimeline, null);
|
||||
|
||||
mSysuiLog.log(new Event("msg"));
|
||||
assertEquals(mSysuiLog.mTimeline, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogEnabled_logWritten() {
|
||||
mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
|
||||
assertEquals(mSysuiLog.mTimeline.size(), 0);
|
||||
|
||||
mSysuiLog.log(new Event("msg"));
|
||||
assertEquals(mSysuiLog.mTimeline.size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxLogs() {
|
||||
mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
|
||||
assertEquals(mSysuiLog.mTimeline.size(), 0);
|
||||
|
||||
final String msg = "msg";
|
||||
for (int i = 0; i < MAX_LOGS + 1; i++) {
|
||||
mSysuiLog.log(new Event(msg + i));
|
||||
}
|
||||
|
||||
assertEquals(mSysuiLog.mTimeline.size(), MAX_LOGS);
|
||||
|
||||
// check the first message (msg0) is deleted:
|
||||
assertEquals(mSysuiLog.mTimeline.getFirst().getMessage(), msg + "1");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user