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

Commit 0a4faab0 authored by Ned Burns's avatar Ned Burns Committed by Android (Google) Code Review
Browse files

Merge "Move NEM and friends over to LogBuffer"

parents dea1e8af afe77bc2
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