1 /* 2 * Copyright (C) 2023 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 package com.android.hoststubgen 17 18 import java.io.BufferedOutputStream 19 import java.io.FileOutputStream 20 import java.io.PrintWriter 21 import java.io.Writer 22 23 val log: HostStubGenLogger = HostStubGenLogger().setConsoleLogLevel(LogLevel.Info) 24 25 /** Logging level */ 26 enum class LogLevel { 27 None, 28 Error, 29 Warn, 30 Info, 31 Verbose, 32 Debug, 33 } 34 35 /** 36 * Simple logging class. 37 * 38 * By default, it has no printers set. Use [setConsoleLogLevel] or [addFilePrinter] to actually 39 * write log. 40 */ 41 class HostStubGenLogger { 42 private var indentLevel: Int = 0 43 get() = field 44 set(value) { 45 field = value 46 indent = " ".repeat(value) 47 } 48 private var indent: String = "" 49 50 private val printers: MutableList<LogPrinter> = mutableListOf() 51 52 private var consolePrinter: LogPrinter? = null 53 54 private var maxLogLevel = LogLevel.None 55 updateMaxLogLevelnull56 private fun updateMaxLogLevel() { 57 maxLogLevel = LogLevel.None 58 59 printers.forEach { 60 if (maxLogLevel < it.logLevel) { 61 maxLogLevel = it.logLevel 62 } 63 } 64 } 65 addPrinternull66 private fun addPrinter(printer: LogPrinter) { 67 printers.add(printer) 68 updateMaxLogLevel() 69 } 70 removePrinternull71 private fun removePrinter(printer: LogPrinter) { 72 printers.remove(printer) 73 updateMaxLogLevel() 74 } 75 setConsoleLogLevelnull76 fun setConsoleLogLevel(level: LogLevel): HostStubGenLogger { 77 // If there's already a console log printer set, remove it, and then add a new one 78 consolePrinter?.let { 79 removePrinter(it) 80 } 81 val cp = StreamPrinter(level, PrintWriter(System.out)) 82 addPrinter(cp) 83 consolePrinter = cp 84 85 return this 86 } 87 addFilePrinternull88 fun addFilePrinter(level: LogLevel, logFilename: String): HostStubGenLogger { 89 addPrinter(StreamPrinter(level, PrintWriter(BufferedOutputStream( 90 FileOutputStream(logFilename))))) 91 92 log.i("Log file set: $logFilename for $level") 93 94 return this 95 } 96 97 /** Flush all the printers */ flushnull98 fun flush() { 99 printers.forEach { it.flush() } 100 } 101 indentnull102 fun indent() { 103 indentLevel++ 104 } 105 unindentnull106 fun unindent() { 107 if (indentLevel <= 0) { 108 throw IllegalStateException("Unbalanced unindent() call.") 109 } 110 indentLevel-- 111 } 112 withIndentnull113 inline fun <T> withIndent(block: () -> T): T { 114 try { 115 indent() 116 return block() 117 } finally { 118 unindent() 119 } 120 } 121 isEnablednull122 fun isEnabled(level: LogLevel): Boolean { 123 return level.ordinal <= maxLogLevel.ordinal 124 } 125 printlnnull126 fun println(level: LogLevel, message: String) { 127 if (message.isEmpty()) { 128 return // Don't print an empty message. 129 } 130 printers.forEach { 131 if (it.logLevel.ordinal >= level.ordinal) { 132 it.println(level, indent, message) 133 } 134 } 135 } 136 printlnnull137 fun println(level: LogLevel, format: String, vararg args: Any?) { 138 if (isEnabled(level)) { 139 println(level, String.format(format, *args)) 140 } 141 } 142 143 /** Log an error. */ enull144 fun e(message: String) { 145 println(LogLevel.Error, message) 146 } 147 148 /** Log an error. */ enull149 fun e(format: String, vararg args: Any?) { 150 println(LogLevel.Error, format, *args) 151 } 152 153 /** Log a warning. */ wnull154 fun w(message: String) { 155 println(LogLevel.Warn, message) 156 } 157 158 /** Log a warning. */ wnull159 fun w(format: String, vararg args: Any?) { 160 println(LogLevel.Warn, format, *args) 161 } 162 163 /** Log an info message. */ inull164 fun i(message: String) { 165 println(LogLevel.Info, message) 166 } 167 168 /** Log an info message. */ inull169 fun i(format: String, vararg args: Any?) { 170 println(LogLevel.Info, format, *args) 171 } 172 173 /** Log a verbose message. */ vnull174 fun v(message: String) { 175 println(LogLevel.Verbose, message) 176 } 177 178 /** Log a verbose message. */ vnull179 fun v(format: String, vararg args: Any?) { 180 println(LogLevel.Verbose, format, *args) 181 } 182 183 /** Log a debug message. */ dnull184 fun d(message: String) { 185 println(LogLevel.Debug, message) 186 } 187 188 /** Log a debug message. */ dnull189 fun d(format: String, vararg args: Any?) { 190 println(LogLevel.Debug, format, *args) 191 } 192 logTimenull193 inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): Double { 194 var ret: Double = -1.0 195 val start = System.currentTimeMillis() 196 try { 197 block() 198 } finally { 199 val end = System.currentTimeMillis() 200 ret = (end - start) / 1000.0 201 if (isEnabled(level)) { 202 println(level, 203 String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0)) 204 } 205 } 206 return ret 207 } 208 209 /** Do an "i" log with how long it took. */ iTimenull210 inline fun <T> iTime(message: String, block: () -> T): Double { 211 return logTime(LogLevel.Info, message, block) 212 } 213 214 /** Do a "v" log with how long it took. */ vTimenull215 inline fun <T> vTime(message: String, block: () -> T): Double { 216 return logTime(LogLevel.Verbose, message, block) 217 } 218 219 /** Do a "d" log with how long it took. */ dTimenull220 inline fun <T> dTime(message: String, block: () -> T): Double { 221 return logTime(LogLevel.Debug, message, block) 222 } 223 224 /** 225 * Similar to the other "xTime" methods, but the message is not supposed to be printed. 226 * It's only used to measure the duration with the same interface as other log methods. 227 */ nTimenull228 inline fun <T> nTime(block: () -> T): Double { 229 return logTime(LogLevel.Debug, "", block) 230 } 231 forVerbosenull232 inline fun forVerbose(block: () -> Unit) { 233 if (isEnabled(LogLevel.Verbose)) { 234 block() 235 } 236 } 237 forDebugnull238 inline fun forDebug(block: () -> Unit) { 239 if (isEnabled(LogLevel.Debug)) { 240 block() 241 } 242 } 243 244 /** Return a Writer for a given log level. */ getWriternull245 fun getWriter(level: LogLevel): Writer { 246 return MultiplexingWriter(level) 247 } 248 249 private inner class MultiplexingWriter(val level: LogLevel) : Writer() { forPrintersnull250 private inline fun forPrinters(callback: (LogPrinter) -> Unit) { 251 printers.forEach { 252 if (it.logLevel.ordinal >= level.ordinal) { 253 callback(it) 254 } 255 } 256 } 257 closenull258 override fun close() { 259 flush() 260 } 261 flushnull262 override fun flush() { 263 forPrinters { 264 it.flush() 265 } 266 } 267 writenull268 override fun write(cbuf: CharArray, off: Int, len: Int) { 269 // TODO Apply indent 270 forPrinters { 271 it.write(cbuf, off, len) 272 } 273 } 274 } 275 276 /** 277 * Handle log-related command line arguments. 278 */ maybeHandleCommandLineArgnull279 fun maybeHandleCommandLineArg(currentArg: String, nextArgProvider: () -> String): Boolean { 280 when (currentArg) { 281 "-v", "--verbose" -> setConsoleLogLevel(LogLevel.Verbose) 282 "-d", "--debug" -> setConsoleLogLevel(LogLevel.Debug) 283 "-q", "--quiet" -> setConsoleLogLevel(LogLevel.None) 284 "--verbose-log" -> addFilePrinter(LogLevel.Verbose, nextArgProvider()) 285 "--debug-log" -> addFilePrinter(LogLevel.Debug, nextArgProvider()) 286 else -> return false 287 } 288 return true 289 } 290 } 291 292 private interface LogPrinter { 293 val logLevel: LogLevel 294 printlnnull295 fun println(logLevel: LogLevel, indent: String, message: String) 296 297 // TODO: This should be removed once MultiplexingWriter starts applying indent, at which point 298 // println() should be used instead. 299 fun write(cbuf: CharArray, off: Int, len: Int) 300 301 fun flush() 302 } 303 304 private class StreamPrinter( 305 override val logLevel: LogLevel, 306 val out: PrintWriter, 307 ) : LogPrinter { 308 override fun println(logLevel: LogLevel, indent: String, message: String) { 309 out.print(indent) 310 out.println(message) 311 } 312 313 override fun write(cbuf: CharArray, off: Int, len: Int) { 314 out.write(cbuf, off, len) 315 } 316 317 override fun flush() { 318 out.flush() 319 } 320 } 321