diff options
| author | 2023-06-07 03:17:21 +0000 | |
|---|---|---|
| committer | 2023-06-27 21:53:27 +0000 | |
| commit | 8ef1cef2da40a012d1a7dc9c1495897965485be1 (patch) | |
| tree | 7ed605a7fa20b506e8e04aab2d181e668fdb7341 | |
| parent | 2be74e83fd1a463279730105e3e37ae41b63038c (diff) | |
Introduce Logger
This change introduces Logger, which is a wrapper class around
LogBuffer, providing a list of convenience methods. Directly logging
through the LogBuffer is now deprecated in favor of the Logger.
Bug: 276475093
Test: atest LogBufferTest LoggerTest
Change-Id: I5cdc5fb49657729735d81cf4a70b168e38b3678d
5 files changed, 423 insertions, 11 deletions
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt index 885873824d1c..e0051f59469d 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt @@ -21,6 +21,7 @@ import android.util.Log import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.log.core.MessageInitializer import com.android.systemui.log.core.MessagePrinter import com.google.errorprone.annotations.CompileTimeConstant @@ -77,7 +78,7 @@ constructor( private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, private val systrace: Boolean = true, -) { +) : MessageBuffer { private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } private val echoMessageQueue: BlockingQueue<LogMessage>? = @@ -178,11 +179,11 @@ constructor( * store any relevant data on the message and then call [commit]. */ @Synchronized - fun obtain( + override fun obtain( tag: String, level: LogLevel, messagePrinter: MessagePrinter, - exception: Throwable? = null, + exception: Throwable?, ): LogMessage { if (!mutable) { return FROZEN_MESSAGE @@ -199,7 +200,7 @@ constructor( * have finished filling in its data fields. The message will be echoed to logcat if necessary. */ @Synchronized - fun commit(message: LogMessage) { + override fun commit(message: LogMessage) { if (!mutable) { return } diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt new file mode 100644 index 000000000000..5729ab270487 --- /dev/null +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2023 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.core + +import com.google.errorprone.annotations.CompileTimeConstant + +/** Logs messages to the [MessageBuffer] with [tag]. */ +open class Logger(val buffer: MessageBuffer, val tag: String) { + /** + * Logs a message to the buffer. + * + * 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 [messageInitializer]. 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 [messagePrinter] function is called on the message. It reads whatever data the + * initializer stored and converts it to a human-readable log message. + * + * @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 messagePrinter 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. + * @param exception Provide any exception that need to be logged. This is saved as + * [LogMessage.exception] + * @param messageInitializer 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. + */ + @JvmOverloads + inline fun log( + level: LogLevel, + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) { + val message = buffer.obtain(tag, level, messagePrinter, exception) + messageInitializer(message) + buffer.commit(message) + } + + /** + * Logs a compile-time string constant [message] to the log buffer. Use sparingly. + * + * This is for simpler use-cases where [message] is a compile time string constant. For + * use-cases where the log message is built during runtime, use the [log] overloaded method that + * takes in an initializer and a message printer. + * + * Buffers are limited by the number of entries, so logging more frequently will limit the time + * window that the [MessageBuffer] covers in a bug report. Richer logs, on the other hand, make + * a bug report more actionable, so using the [log] with a [MessagePrinter] to add more details + * to every log may do more to improve overall logging than adding more logs with this method. + */ + @JvmOverloads + fun log( + level: LogLevel, + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(level, { str1!! }, exception) { str1 = message } + + /** + * Logs a message to the buffer at [LogLevel.VERBOSE]. + * + * @see log + */ + @JvmOverloads + inline fun v( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.VERBOSE, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.VERBOSE]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun v( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.VERBOSE, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.DEBUG]. + * + * @see log + */ + @JvmOverloads + inline fun d( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.DEBUG, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.DEBUG]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun d( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.DEBUG, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.INFO]. + * + * @see log + */ + @JvmOverloads + inline fun i( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.INFO, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.INFO]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun i( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.INFO, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.WARNING]. + * + * @see log + */ + @JvmOverloads + inline fun w( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.WARNING, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WARNING]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun w( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.WARNING, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.ERROR]. + * + * @see log + */ + @JvmOverloads + inline fun e( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.ERROR, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.ERROR]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun e( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.ERROR, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.WTF]. + * + * @see log + */ + @JvmOverloads + inline fun wtf( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.WTF, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WTF]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun wtf( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.WTF, message, exception) +} diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt new file mode 100644 index 000000000000..bb91633c4d87 --- /dev/null +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.core + +/** + * [MessageBuffer] is an interface that represents a buffer of log messages, and provides methods to + * [obtain] a log message and [commit] it to the buffer. + */ +interface MessageBuffer { + /** + * Obtains the next [LogMessage] from the buffer. + * + * After calling [obtain], the caller must store any relevant data on the message and then call + * [commit]. + */ + fun obtain( + tag: String, + level: LogLevel, + messagePrinter: MessagePrinter, + exception: Throwable? = null, + ): LogMessage + + /** + * After acquiring a log message via [obtain], call this method to signal to the buffer that + * data fields have been filled. + */ + fun commit(message: LogMessage) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt index 6836733f1580..b5eae5b03b74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt @@ -2,7 +2,7 @@ package com.android.systemui.log import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.Logger import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter @@ -33,7 +33,8 @@ class LogBufferTest : SysuiTestCase() { @Test fun log_shouldSaveLogToBuffer() { - buffer.log("Test", LogLevel.INFO, "Some test message") + val logger = Logger(buffer, "Test") + logger.i("Some test message") val dumpedString = dumpBuffer() @@ -42,8 +43,9 @@ class LogBufferTest : SysuiTestCase() { @Test fun log_shouldRotateIfLogBufferIsFull() { - buffer.log("Test", LogLevel.INFO, "This should be rotated") - buffer.log("Test", LogLevel.INFO, "New test message") + val logger = Logger(buffer, "Test") + logger.i("This should be rotated") + logger.i("New test message") val dumpedString = dumpBuffer() @@ -54,7 +56,8 @@ class LogBufferTest : SysuiTestCase() { fun dump_writesExceptionAndStacktrace() { buffer = createBuffer() val exception = createTestException("Exception message", "TestClass") - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedString = dumpBuffer() @@ -73,7 +76,8 @@ class LogBufferTest : SysuiTestCase() { "TestClass", cause = createTestException("The real cause!", "TestClass") ) - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedString = dumpBuffer() @@ -94,7 +98,8 @@ class LogBufferTest : SysuiTestCase() { ) ) exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass")) - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedStr = dumpBuffer() diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt new file mode 100644 index 000000000000..ab19b3aeceb0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt @@ -0,0 +1,140 @@ +package com.android.systemui.log.core + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.LogMessageImpl +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyString +import org.mockito.Mockito.isNull +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.junit.MockitoJUnitRunner + +@SmallTest +@RunWith(MockitoJUnitRunner::class) +class LoggerTest : SysuiTestCase() { + @Mock private lateinit var buffer: MessageBuffer + private lateinit var message: LogMessage + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(buffer.obtain(any(), any(), any(), isNull())).thenAnswer { + message = LogMessageImpl.Factory.create() + return@thenAnswer message + } + } + + @Test + fun log_shouldCommitLogMessage() { + val logger = Logger(buffer, "LoggerTest") + logger.log(LogLevel.DEBUG, { "count=$int1" }) { + int1 = 1 + str1 = "test" + bool1 = true + } + + assertThat(message.int1).isEqualTo(1) + assertThat(message.str1).isEqualTo("test") + assertThat(message.bool1).isEqualTo(true) + } + + @Test + fun log_shouldUseCorrectLoggerTag() { + val logger = Logger(buffer, "LoggerTest") + logger.log(LogLevel.DEBUG, { "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(eq("LoggerTest"), any(), any(), nullable()) + } + + @Test + fun v_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.v({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable()) + } + + @Test + fun v_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.v("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable()) + } + + @Test + fun d_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.d({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable()) + } + + @Test + fun d_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.d("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable()) + } + + @Test + fun i_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.i({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable()) + } + + @Test + fun i_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.i("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable()) + } + + @Test + fun w_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.w({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable()) + } + + @Test + fun w_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.w("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable()) + } + + @Test + fun e_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.e({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable()) + } + + @Test + fun e_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.e("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable()) + } + + @Test + fun wtf_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.wtf({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable()) + } + + @Test + fun wtf_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.wtf("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable()) + } +} |