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

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

Merge changes I5fe67e17,I2c7c272c

* changes:
  Example implementations of new logging system
  Hyper-lightweight logging update
parents bd35832d 7d2e9c1c
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.DumpController;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.Recents;
@@ -59,9 +60,12 @@ import dagger.Provides;
 * A dagger module for injecting components of System UI that are not overridden by the System UI
 * implementation.
 */
@Module(includes = {AssistModule.class,
@Module(includes = {
            AssistModule.class,
            ConcurrencyModule.class,
                    PeopleHubModule.class},
            LogModule.class,
            PeopleHubModule.class,
        },
        subcomponents = {StatusBarComponent.class, NotificationRowComponent.class})
public abstract class SystemUIModule {

+213 −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.log

import android.util.Log
import com.android.systemui.DumpController
import com.android.systemui.Dumpable
import com.android.systemui.log.dagger.LogModule
import java.text.SimpleDateFormat
import java.util.ArrayDeque
import java.util.Locale

/**
 * A simple ring buffer of recyclable log messages
 *
 * The goal of this class is to enable logging that is both extremely chatty and extremely
 * lightweight. If done properly, logging a message will not result in any heap allocations or
 * string generation. Messages are only converted to strings if the log is actually dumped (usually
 * as the result of taking a bug report).
 *
 * You can dump the entire buffer at any time by running:
 *
 * ```
 * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService \
 *      dependency DumpController <bufferName>
 * ```
 *
 * where `bufferName` is the (case-sensitive) [name] passed to the constructor.
 *
 * By default, only messages of WARN level or higher are echoed to logcat, but this can be adjusted
 * locally (usually for debugging purposes).
 *
 * To enable logcat echoing for an entire buffer:
 *
 * ```
 * $ adb shell settings put global systemui/buffer/<bufferName> <level>
 * ```
 *
 * To enable logcat echoing for a specific tag:
 *
 * ```
 * $ adb shell settings put global systemui/tag/<tag> <level>
 * ```
 *
 * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
 * the first letter of any of the previous.
 *
 * Buffers are provided by [LogModule].
 *
 * @param name The name of this buffer
 * @param maxLogs The maximum number of messages to keep in memory at any one time, including the
 * unused pool.
 * @param poolSize The maximum amount that the size of the buffer is allowed to flex in response to
 * sequential calls to [document] that aren't immediately followed by a matching call to [push].
 */
class LogBuffer(
    private val name: String,
    private val maxLogs: Int,
    private val poolSize: Int,
    private val logcatEchoTracker: LogcatEchoTracker
) {
    private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()

    fun attach(dumpController: DumpController) {
        dumpController.registerDumpable(name, onDump)
    }

    /**
     * Logs a message to the log buffer
     *
     * May also log the message to logcat if echoing is enabled for this buffer or tag.
     *
     * The actual string of the log message is not constructed until it is needed. To accomplish
     * this, logging a message is a two-step process. First, a fresh instance  of [LogMessage] is
     * obtained and is passed to the [initializer]. The initializer stores any relevant data on the
     * message's fields. The message is then inserted into the buffer where it waits until it is
     * either pushed out by newer messages or it needs to printed. If and when this latter moment
     * occurs, the [printer] function is called on the message. It reads whatever data the
     * initializer stored and converts it to a human-readable log message.
     *
     * @param tag A string of at most 23 characters, used for grouping logs into categories or
     * subjects. If this message is echoed to logcat, this will be the tag that is used.
     * @param level Which level to log the message at, both to the buffer and to logcat if it's
     * echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
     * INFO level should be reserved for information that other parts of the system might care
     * about, leaving the specifics of code's day-to-day operations to DEBUG.
     * @param initializer A function that will be called immediately to store relevant data on the
     * log message. The value of `this` will be the LogMessage to be initialized.
     * @param printer A function that will be called if and when the message needs to be dumped to
     * logcat or a bug report. It should read the data stored by the initializer and convert it to
     * a human-readable string. The value of `this` will be the LogMessage to be printed.
     * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
     * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
     * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
     */
    inline fun log(
        tag: String,
        level: LogLevel,
        initializer: LogMessage.() -> Unit,
        noinline printer: LogMessage.() -> String
    ) {
        val message = obtain(tag, level, printer)
        initializer(message)
        push(message)
    }

    /**
     * Same as [log], but doesn't push the message to the buffer. Useful if you need to supply a
     * "reason" for doing something (the thing you supply the reason to will presumably call [push]
     * on that message at some point).
     */
    inline fun document(
        tag: String,
        level: LogLevel,
        initializer: LogMessage.() -> Unit,
        noinline printer: LogMessage.() -> String
    ): LogMessage {
        val message = obtain(tag, level, printer)
        initializer(message)
        return message
    }

    /**
     * Obtains an instance of [LogMessageImpl], usually from the object pool. If the pool has been
     * exhausted, creates a new instance.
     *
     * In general, you should call [log] or [document] instead of this method.
     */
    fun obtain(
        tag: String,
        level: LogLevel,
        printer: (LogMessage) -> String
    ): LogMessageImpl {
        val message = synchronized(buffer) {
            if (buffer.size > maxLogs - poolSize) {
                buffer.removeFirst()
            } else {
                LogMessageImpl.create()
            }
        }
        message.reset(tag, level, System.currentTimeMillis(), printer)
        return message
    }

    /**
     * Pushes a message into buffer, possibly evicting an older message if the buffer is full.
     */
    fun push(message: LogMessage) {
        synchronized(buffer) {
            if (buffer.size == maxLogs) {
                Log.e(TAG, "LogBuffer $name has exceeded its pool size")
                buffer.removeFirst()
            }
            buffer.add(message as LogMessageImpl)
            if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
                    logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
                echoToLogcat(message)
            }
        }
    }

    /** Converts the entire buffer to a newline-delimited string */
    fun dump(): String {
        synchronized(buffer) {
            val sb = StringBuilder()
            for (message in buffer) {
                dumpMessage(message, sb)
            }
            return sb.toString()
        }
    }

    private fun dumpMessage(message: LogMessage, sb: StringBuilder) {
        sb.append(DATE_FORMAT.format(message.timestamp))
                .append(" ").append(message.level)
                .append(" ").append(message.tag)
                .append(" ").append(message.printer(message))
                .append("\n")
    }

    private fun echoToLogcat(message: LogMessage) {
        val strMessage = message.printer(message)
        when (message.level) {
            LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
            LogLevel.DEBUG -> Log.d(message.tag, strMessage)
            LogLevel.INFO -> Log.i(message.tag, strMessage)
            LogLevel.WARNING -> Log.w(message.tag, strMessage)
            LogLevel.ERROR -> Log.e(message.tag, strMessage)
            LogLevel.WTF -> Log.wtf(message.tag, strMessage)
        }
    }

    private val onDump = Dumpable { _, pw, _ ->
        pw.println(dump())
    }
}

private const val TAG = "LogBuffer"
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US)
+31 −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.log

import android.util.Log

/**
 * Enum version of @Log.Level
 */
enum class LogLevel(@Log.Level val nativeLevel: Int) {
    VERBOSE(Log.VERBOSE),
    DEBUG(Log.DEBUG),
    INFO(Log.INFO),
    WARNING(Log.WARN),
    ERROR(Log.ERROR),
    WTF(Log.ASSERT)
}
+47 −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.log

/**
 * Generic data class for storing messages logged to a [LogBuffer]
 *
 * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic
 * data slots that may or may not be used, depending on the nature of the specific message being
 * logged.
 *
 * When a message is logged, the code doing the logging stores data in one or more of the generic
 * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
 * [printer] function reads the data stored in the generic fields and converts that to a human-
 * readable string. Thus, for every log type there must be a specialized initializer function that
 * stores data specific to that log type and a specialized printer function that prints that data.
 *
 * See [LogBuffer.log] for more information.
 */
interface LogMessage {
    val level: LogLevel
    val tag: String
    val timestamp: Long
    val printer: LogMessage.() -> String

    var str1: String?
    var str2: String?
    var str3: String?
    var int1: Int
    var int2: Int
    var long1: Long
    var double1: Double
}
+74 −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.log

/**
 * Recyclable implementation of [LogMessage].
 */
data class LogMessageImpl(
    override var level: LogLevel,
    override var tag: String,
    override var timestamp: Long,
    override var printer: LogMessage.() -> String,
    override var str1: String?,
    override var str2: String?,
    override var str3: String?,
    override var int1: Int,
    override var int2: Int,
    override var long1: Long,
    override var double1: Double
) : LogMessage {

    fun reset(
        tag: String,
        level: LogLevel,
        timestamp: Long,
        renderer: LogMessage.() -> String
    ) {
        this.level = level
        this.tag = tag
        this.timestamp = timestamp
        this.printer = renderer
        str1 = null
        str2 = null
        str3 = null
        int1 = 0
        int2 = 0
        long1 = 0
        double1 = 0.0
    }

    companion object Factory {
        fun create(): LogMessageImpl {
            return LogMessageImpl(
                    LogLevel.DEBUG,
                    DEFAULT_TAG,
                    0,
                    DEFAULT_RENDERER,
                    null,
                    null,
                    null,
                    0,
                    0,
                    0,
                    0.0)
        }
    }
}

private const val DEFAULT_TAG = "UnknownTag"
private val DEFAULT_RENDERER: LogMessage.() -> String = { "Unknown message: $this" }
Loading