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