Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit afe77bc2 authored by Ned Burns's avatar Ned Burns
Browse files

Move NEM and friends over to LogBuffer

Test: atest
Change-Id: I276d1253094ac160b3738bf01fc00bc94d767756
parent 3ec4f573
Loading
Loading
Loading
Loading
+0 −84
Original line number Diff line number Diff line
/*
 * 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;
    public static final @Level int DEFAULT_LOG_LEVEL = DEBUG;

    private long mTimestamp;
    private @Level int mLogLevel = DEFAULT_LOG_LEVEL;
    private String mMessage = "";

    /**
     * initialize an event with a message
     */
    public Event init(String message) {
        init(DEFAULT_LOG_LEVEL, message);
        return this;
    }

    /**
     * initialize an event with a logLevel and message
     */
    public Event init(@Level int logLevel, String message) {
        mTimestamp = System.currentTimeMillis();
        mLogLevel = logLevel;
        mMessage = message;
        return this;
    }

    public String getMessage() {
        return mMessage;
    }

    public long getTimestamp() {
        return mTimestamp;
    }

    public @Level int getLogLevel() {
        return mLogLevel;
    }

    /**
     * Recycle this event
     */
    void recycle() {
        mTimestamp = -1;
        mLogLevel = DEFAULT_LOG_LEVEL;
        mMessage = "";
    }
}
+0 −123
Original line number Diff line number Diff line
/*
 * 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 int mType;

    /**
     * Initializes a rich event that includes an event type that matches with an index in the array
     * getEventLabels().
     */
    public RichEvent init(@Event.Level int logLevel, int type, String reason) {
        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;
        super.init(logLevel, getEventLabels()[mType] + " " + reason);
        return this;
    }

    /**
     * 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();

    @Override
    public void recycle() {
        super.recycle();
        mType = -1;
    }

    public int getType() {
        return mType;
    }

    /**
     * Builder to build a RichEvent.
     * @param <B> Log specific builder that is extending this builder
     * @param <E> Type of event we'll be building
     */
    public abstract static class Builder<B extends Builder<B, E>, E extends RichEvent> {
        public static final int UNINITIALIZED = -1;

        public final SysuiLog mLog;
        private B mBuilder = getBuilder();
        protected int mType;
        protected String mReason;
        protected @Level int mLogLevel;

        public Builder(SysuiLog sysuiLog) {
            mLog = sysuiLog;
            reset();
        }

        /**
         * Reset this builder's parameters so it can be reused to build another RichEvent.
         */
        public void reset() {
            mType = UNINITIALIZED;
            mReason = null;
            mLogLevel = VERBOSE;
        }

        /**
         * Get the log-specific builder.
         */
        public abstract B getBuilder();

        /**
         * Build the log-specific event given an event to populate.
         */
        public abstract E build(E e);

        /**
         * 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;
        }
    }
}
+0 −180
Original line number Diff line number Diff line
/*
 * 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.S", 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;
}
+14 −24
Original line number Diff line number Diff line
@@ -47,8 +47,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationRankin
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -129,6 +127,8 @@ public class NotificationEntryManager implements
    private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
            new ArrayMap<>();

    private final NotificationEntryManagerLogger mLogger;

    // Lazily retrieved dependencies
    private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
    private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
@@ -143,7 +143,6 @@ public class NotificationEntryManager implements

    private NotificationPresenter mPresenter;
    private RankingMap mLatestRankingMap;
    private NotifLog mNotifLog;

    @VisibleForTesting
    final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
@@ -184,7 +183,7 @@ public class NotificationEntryManager implements

    @Inject
    public NotificationEntryManager(
            NotifLog notifLog,
            NotificationEntryManagerLogger logger,
            NotificationGroupManager groupManager,
            NotificationRankingManager rankingManager,
            KeyguardEnvironment keyguardEnvironment,
@@ -193,7 +192,7 @@ public class NotificationEntryManager implements
            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
            LeakDetector leakDetector,
            ForegroundServiceDismissalFeatureController fgsFeatureController) {
        mNotifLog = notifLog;
        mLogger = logger;
        mGroupManager = groupManager;
        mRankingManager = rankingManager;
        mKeyguardEnvironment = keyguardEnvironment;
@@ -291,13 +290,12 @@ public class NotificationEntryManager implements
            NotificationEntry entry = mPendingNotifications.get(key);
            entry.abortTask();
            mPendingNotifications.remove(key);
            mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry, "PendingNotification aborted"
                    + " reason=" + reason);
            mLogger.logInflationAborted(key, "pending", reason);
        }
        NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
        if (addedEntry != null) {
            addedEntry.abortTask();
            mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getKey() + " " + reason);
            mLogger.logInflationAborted(key, "active", reason);
        }
    }

@@ -328,9 +326,9 @@ public class NotificationEntryManager implements
        // the list, otherwise we might get leaks.
        if (!entry.isRowRemoved()) {
            boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null;
            mLogger.logNotifInflated(entry.getKey(), isNew);
            if (isNew) {
                for (NotificationEntryListener listener : mNotificationEntryListeners) {
                    mNotifLog.log(NotifEvent.INFLATED, entry);
                    listener.onEntryInflated(entry);
                }
                addActiveNotification(entry);
@@ -340,7 +338,6 @@ public class NotificationEntryManager implements
                }
            } else {
                for (NotificationEntryListener listener : mNotificationEntryListeners) {
                    mNotifLog.log(NotifEvent.INFLATED, entry);
                    listener.onEntryReinflated(entry);
                }
            }
@@ -422,7 +419,7 @@ public class NotificationEntryManager implements
        for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
            if (interceptor.onNotificationRemoveRequested(key, entry, reason)) {
                // Remove intercepted; log and skip
                mNotifLog.log(NotifEvent.REMOVE_INTERCEPTED);
                mLogger.logRemovalIntercepted(key);
                return;
            }
        }
@@ -437,10 +434,7 @@ public class NotificationEntryManager implements
                    if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
                        extendLifetime(pendingEntry, extender);
                        lifetimeExtended = true;
                        mNotifLog.log(
                                NotifEvent.LIFETIME_EXTENDED,
                                pendingEntry.getSbn(),
                                "pendingEntry extendedBy=" + extender.toString());
                        mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending");
                    }
                }
            }
@@ -460,10 +454,7 @@ public class NotificationEntryManager implements
                        mLatestRankingMap = ranking;
                        extendLifetime(entry, extender);
                        lifetimeExtended = true;
                        mNotifLog.log(
                                NotifEvent.LIFETIME_EXTENDED,
                                entry.getSbn(),
                                "entry extendedBy=" + extender.toString());
                        mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active");
                        break;
                    }
                }
@@ -486,8 +477,7 @@ public class NotificationEntryManager implements
                mLeakDetector.trackGarbage(entry);
                removedByUser |= entryDismissed;

                mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(),
                        "removedByUser=" + removedByUser);
                mLogger.logNotifRemoved(entry.getKey(), removedByUser);
                for (NotificationEntryListener listener : mNotificationEntryListeners) {
                    listener.onEntryRemoved(entry, visibility, removedByUser);
                }
@@ -576,7 +566,7 @@ public class NotificationEntryManager implements

        abortExistingInflation(key, "addNotification");
        mPendingNotifications.put(key, entry);
        mNotifLog.log(NotifEvent.NOTIF_ADDED, entry);
        mLogger.logNotifAdded(entry.getKey());
        for (NotificationEntryListener listener : mNotificationEntryListeners) {
            listener.onPendingEntryAdded(entry);
        }
@@ -613,7 +603,7 @@ public class NotificationEntryManager implements
        entry.setSbn(notification);
        mGroupManager.onEntryUpdated(entry, oldSbn);

        mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry);
        mLogger.logNotifUpdated(entry.getKey());
        for (NotificationEntryListener listener : mNotificationEntryListeners) {
            listener.onPreEntryUpdated(entry);
        }
@@ -808,7 +798,7 @@ public class NotificationEntryManager implements
    //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking
    /**
     * @param rankingMap the {@link RankingMap} to apply to the current notification list
     * @param reason the reason for calling this method, for {@link NotifLog}
     * @param reason the reason for calling this method, which will be logged
     */
    public void updateRanking(RankingMap rankingMap, String reason) {
        updateRankingAndSort(rankingMap, reason);
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.statusbar.notification

import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
import javax.inject.Inject

/** Logger for [NotificationEntryManager]. */
class NotificationEntryManagerLogger @Inject constructor(
    @NotificationLog private val buffer: LogBuffer
) {
    fun logNotifAdded(key: String) {
        buffer.log(TAG, INFO, {
            str1 = key
        }, {
            "NOTIF ADDED $str1"
        })
    }

    fun logNotifUpdated(key: String) {
        buffer.log(TAG, INFO, {
            str1 = key
        }, {
            "NOTIF UPDATED $str1"
        })
    }

    fun logInflationAborted(key: String, status: String, reason: String) {
        buffer.log(TAG, DEBUG, {
            str1 = key
            str2 = status
            str3 = reason
        }, {
            "NOTIF INFLATION ABORTED $str1 notifStatus=$str2 reason=$str3"
        })
    }

    fun logNotifInflated(key: String, isNew: Boolean) {
        buffer.log(TAG, DEBUG, {
            str1 = key
            bool1 = isNew
        }, {
            "NOTIF INFLATED $str1 isNew=$bool1}"
        })
    }

    fun logRemovalIntercepted(key: String) {
        buffer.log(TAG, INFO, {
            str1 = key
        }, {
            "NOTIF REMOVE INTERCEPTED for $str1"
        })
    }

    fun logLifetimeExtended(key: String, extenderName: String, status: String) {
        buffer.log(TAG, INFO, {
            str1 = key
            str2 = extenderName
            str3 = status
        }, {
            "NOTIF LIFETIME EXTENDED $str1 extender=$str2 status=$str3"
        })
    }

    fun logNotifRemoved(key: String, removedByUser: Boolean) {
        buffer.log(TAG, INFO, {
            str1 = key
            bool1 = removedByUser
        }, {
            "NOTIF REMOVED $str1 removedByUser=$bool1"
        })
    }

    fun logFilterAndSort(reason: String) {
        buffer.log(TAG, INFO, {
            str1 = reason
        }, {
            "FILTER AND SORT reason=$str1"
        })
    }
}

private const val TAG = "NotificationEntryMgr"
 No newline at end of file
Loading