• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.plugins.log
18 
19 import android.os.Trace
20 import android.util.Log
21 import com.android.systemui.plugins.util.RingBuffer
22 import com.google.errorprone.annotations.CompileTimeConstant
23 import java.io.PrintWriter
24 import java.util.concurrent.ArrayBlockingQueue
25 import java.util.concurrent.BlockingQueue
26 import kotlin.concurrent.thread
27 import kotlin.math.max
28 
29 /**
30  * A simple ring buffer of recyclable log messages
31  *
32  * The goal of this class is to enable logging that is both extremely chatty and extremely
33  * lightweight. If done properly, logging a message will not result in any heap allocations or
34  * string generation. Messages are only converted to strings if the log is actually dumped (usually
35  * as the result of taking a bug report).
36  *
37  * You can dump the entire buffer at any time by running:
38  * ```
39  * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService <bufferName>
40  * ```
41  *
42  * ...where `bufferName` is the (case-sensitive) [name] passed to the constructor.
43  *
44  * By default, only messages of WARN level or higher are echoed to logcat, but this can be adjusted
45  * locally (usually for debugging purposes).
46  *
47  * To enable logcat echoing for an entire buffer:
48  * ```
49  * $ adb shell settings put global systemui/buffer/<bufferName> <level>
50  * ```
51  *
52  * To enable logcat echoing for a specific tag:
53  * ```
54  * $ adb shell settings put global systemui/tag/<tag> <level>
55  * ```
56  *
57  * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
58  * the first letter of any of the previous.
59  *
60  * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI
61  * LogBufferFactory.
62  *
63  * @param name The name of this buffer, printed when the buffer is dumped and in some other
64  *   situations.
65  * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
66  *   out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
67  *   the maximum, it behaves like a ring buffer.
68  */
69 class LogBuffer
70 @JvmOverloads
71 constructor(
72     private val name: String,
73     private val maxSize: Int,
74     private val logcatEchoTracker: LogcatEchoTracker,
75     private val systrace: Boolean = true,
76 ) {
<lambda>null77     private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
78 
79     private val echoMessageQueue: BlockingQueue<LogMessage>? =
80         if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
81 
82     init {
83         if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
<lambda>null84             thread(start = true, name = "LogBuffer-$name", priority = Thread.NORM_PRIORITY) {
85                 try {
86                     while (true) {
87                         echoToDesiredEndpoints(echoMessageQueue.take())
88                     }
89                 } catch (e: InterruptedException) {
90                     Thread.currentThread().interrupt()
91                 }
92             }
93         }
94     }
95 
96     var frozen = false
97         private set
98 
99     private val mutable
100         get() = !frozen && maxSize > 0
101 
102     /**
103      * Logs a message to the log buffer
104      *
105      * May also log the message to logcat if echoing is enabled for this buffer or tag.
106      *
107      * The actual string of the log message is not constructed until it is needed. To accomplish
108      * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
109      * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
110      * on the message's fields. The message is then inserted into the buffer where it waits until it
111      * is either pushed out by newer messages or it needs to printed. If and when this latter moment
112      * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
113      * initializer stored and converts it to a human-readable log message.
114      *
115      * @param tag A string of at most 23 characters, used for grouping logs into categories or
116      *   subjects. If this message is echoed to logcat, this will be the tag that is used.
117      * @param level Which level to log the message at, both to the buffer and to logcat if it's
118      *   echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
119      *   INFO level should be reserved for information that other parts of the system might care
120      *   about, leaving the specifics of code's day-to-day operations to DEBUG.
121      * @param messageInitializer A function that will be called immediately to store relevant data
122      *   on the log message. The value of `this` will be the LogMessage to be initialized.
123      * @param messagePrinter A function that will be called if and when the message needs to be
124      *   dumped to logcat or a bug report. It should read the data stored by the initializer and
125      *   convert it to a human-readable string. The value of `this` will be the LogMessage to be
126      *   printed. **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and
127      *   NEVER any variables in its enclosing scope. Otherwise, the runtime will need to allocate a
128      *   new instance of the printer for each call, thwarting our attempts at avoiding any sort of
129      *   allocation.
130      * @param exception Provide any exception that need to be logged. This is saved as
131      *   [LogMessage.exception]
132      */
133     @JvmOverloads
lognull134     inline fun log(
135         tag: String,
136         level: LogLevel,
137         messageInitializer: MessageInitializer,
138         noinline messagePrinter: MessagePrinter,
139         exception: Throwable? = null,
140     ) {
141         val message = obtain(tag, level, messagePrinter, exception)
142         messageInitializer(message)
143         commit(message)
144     }
145 
146     /**
147      * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
148      *
149      * May also log the message to logcat if echoing is enabled for this buffer or tag. This is for
150      * simpler use-cases where [message] is a compile time string constant. For use-cases where the
151      * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
152      * an initializer and a message printer.
153      *
154      * Log buffers are limited by the number of entries, so logging more frequently will limit the
155      * time window that the LogBuffer covers in a bug report. Richer logs, on the other hand, make a
156      * bug report more actionable, so using the [log] with a messagePrinter to add more detail to
157      * every log may do more to improve overall logging than adding more logs with this method.
158      */
159     @JvmOverloads
lognull160     fun log(
161         tag: String,
162         level: LogLevel,
163         @CompileTimeConstant message: String,
164         exception: Throwable? = null,
165     ) = log(tag, level, { str1 = message }, { str1!! }, exception)
166 
167     /**
168      * You should call [log] instead of this method.
169      *
170      * Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size,
171      * grows the buffer by one.
172      *
173      * After calling [obtain], the message will now be at the end of the buffer. The caller must
174      * store any relevant data on the message and then call [commit].
175      */
176     @Synchronized
obtainnull177     fun obtain(
178         tag: String,
179         level: LogLevel,
180         messagePrinter: MessagePrinter,
181         exception: Throwable? = null,
182     ): LogMessage {
183         if (!mutable) {
184             return FROZEN_MESSAGE
185         }
186         val message = buffer.advance()
187         message.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
188         return message
189     }
190 
191     /**
192      * You should call [log] instead of this method.
193      *
194      * After acquiring a message via [obtain], call this method to signal to the buffer that you
195      * have finished filling in its data fields. The message will be echoed to logcat if necessary.
196      */
197     @Synchronized
commitnull198     fun commit(message: LogMessage) {
199         if (!mutable) {
200             return
201         }
202         // Log in the background thread only if echoMessageQueue exists and has capacity (checking
203         // capacity avoids the possibility of blocking this thread)
204         if (echoMessageQueue != null && echoMessageQueue.remainingCapacity() > 0) {
205             try {
206                 echoMessageQueue.put(message)
207             } catch (e: InterruptedException) {
208                 // the background thread has been shut down, so just log on this one
209                 echoToDesiredEndpoints(message)
210             }
211         } else {
212             echoToDesiredEndpoints(message)
213         }
214     }
215 
216     /** Sends message to echo after determining whether to use Logcat and/or systrace. */
echoToDesiredEndpointsnull217     private fun echoToDesiredEndpoints(message: LogMessage) {
218         val includeInLogcat =
219             logcatEchoTracker.isBufferLoggable(name, message.level) ||
220                 logcatEchoTracker.isTagLoggable(message.tag, message.level)
221         echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
222     }
223 
224     /** Converts the entire buffer to a newline-delimited string */
225     @Synchronized
dumpnull226     fun dump(pw: PrintWriter, tailLength: Int) {
227         val iterationStart =
228             if (tailLength <= 0) {
229                 0
230             } else {
231                 max(0, buffer.size - tailLength)
232             }
233 
234         for (i in iterationStart until buffer.size) {
235             buffer[i].dump(pw)
236         }
237     }
238 
239     /**
240      * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. Calls
241      * to [log], [obtain], and [commit] will not affect the buffer and will return dummy values if
242      * necessary.
243      */
244     @Synchronized
freezenull245     fun freeze() {
246         if (!frozen) {
247             log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 frozen" })
248             frozen = true
249         }
250     }
251 
252     /** Undoes the effects of calling [freeze]. */
253     @Synchronized
unfreezenull254     fun unfreeze() {
255         if (frozen) {
256             log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" })
257             frozen = false
258         }
259     }
260 
echonull261     private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
262         if (toLogcat || toSystrace) {
263             val strMessage = message.messagePrinter(message)
264             if (toSystrace) {
265                 echoToSystrace(message, strMessage)
266             }
267             if (toLogcat) {
268                 echoToLogcat(message, strMessage)
269             }
270         }
271     }
272 
echoToSystracenull273     private fun echoToSystrace(message: LogMessage, strMessage: String) {
274         Trace.instantForTrack(
275             Trace.TRACE_TAG_APP,
276             "UI Events",
277             "$name - ${message.level.shortString} ${message.tag}: $strMessage"
278         )
279     }
280 
echoToLogcatnull281     private fun echoToLogcat(message: LogMessage, strMessage: String) {
282         when (message.level) {
283             LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
284             LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
285             LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
286             LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
287             LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
288             LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
289         }
290     }
291 }
292 
293 /**
294  * A function that will be called immediately to store relevant data on the log message. The value
295  * of `this` will be the LogMessage to be initialized.
296  */
297 typealias MessageInitializer = LogMessage.() -> Unit
298 
299 private const val TAG = "LogBuffer"
300 private val FROZEN_MESSAGE = LogMessageImpl.create()
301