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         return this
93     }
94 
95     /** Flush all the printers */
flushnull96     fun flush() {
97         printers.forEach { it.flush() }
98     }
99 
indentnull100     fun indent() {
101         indentLevel++
102     }
103 
unindentnull104     fun unindent() {
105         if (indentLevel <= 0) {
106             throw IllegalStateException("Unbalanced unindent() call.")
107         }
108         indentLevel--
109     }
110 
withIndentnull111     inline fun <T> withIndent(block: () -> T): T {
112         try {
113             indent()
114             return block()
115         } finally {
116             unindent()
117         }
118     }
119 
isEnablednull120     fun isEnabled(level: LogLevel): Boolean {
121         return level.ordinal <= maxLogLevel.ordinal
122     }
123 
printlnnull124     private fun println(level: LogLevel, message: String) {
125         printers.forEach {
126             if (it.logLevel.ordinal >= level.ordinal) {
127                 it.println(level, indent, message)
128             }
129         }
130     }
131 
printlnnull132     private fun println(level: LogLevel, format: String, vararg args: Any?) {
133         if (isEnabled(level)) {
134             println(level, String.format(format, *args))
135         }
136     }
137 
138     /** Log an error. */
enull139     fun e(message: String) {
140         println(LogLevel.Error, message)
141     }
142 
143     /** Log an error. */
enull144     fun e(format: String, vararg args: Any?) {
145         println(LogLevel.Error, format, *args)
146     }
147 
148     /** Log a warning. */
wnull149     fun w(message: String) {
150         println(LogLevel.Warn, message)
151     }
152 
153     /** Log a warning. */
wnull154     fun w(format: String, vararg args: Any?) {
155         println(LogLevel.Warn, format, *args)
156     }
157 
158     /** Log an info message. */
inull159     fun i(message: String) {
160         println(LogLevel.Info, message)
161     }
162 
163     /** Log an info message. */
inull164     fun i(format: String, vararg args: Any?) {
165         println(LogLevel.Info, format, *args)
166     }
167 
168     /** Log a verbose message. */
vnull169     fun v(message: String) {
170         println(LogLevel.Verbose, message)
171     }
172 
173     /** Log a verbose message. */
vnull174     fun v(format: String, vararg args: Any?) {
175         println(LogLevel.Verbose, format, *args)
176     }
177 
178     /** Log a debug message. */
dnull179     fun d(message: String) {
180         println(LogLevel.Debug, message)
181     }
182 
183     /** Log a debug message. */
dnull184     fun d(format: String, vararg args: Any?) {
185         println(LogLevel.Debug, format, *args)
186     }
187 
forVerbosenull188     inline fun forVerbose(block: () -> Unit) {
189         if (isEnabled(LogLevel.Verbose)) {
190             block()
191         }
192     }
193 
forDebugnull194     inline fun forDebug(block: () -> Unit) {
195         if (isEnabled(LogLevel.Debug)) {
196             block()
197         }
198     }
199 
200     /** Return a Writer for a given log level. */
getWriternull201     fun getWriter(level: LogLevel): Writer {
202         return MultiplexingWriter(level)
203     }
204 
205     private inner class MultiplexingWriter(val level: LogLevel) : Writer() {
forPrintersnull206         private inline fun forPrinters(callback: (LogPrinter) -> Unit) {
207             printers.forEach {
208                 if (it.logLevel.ordinal >= level.ordinal) {
209                     callback(it)
210                 }
211             }
212         }
213 
closenull214         override fun close() {
215             flush()
216         }
217 
flushnull218         override fun flush() {
219             forPrinters {
220                 it.flush()
221             }
222         }
223 
writenull224         override fun write(cbuf: CharArray, off: Int, len: Int) {
225             // TODO Apply indent
226             forPrinters {
227                 it.write(cbuf, off, len)
228             }
229         }
230     }
231 }
232 
233 private interface LogPrinter {
234     val logLevel: LogLevel
235 
printlnnull236     fun println(logLevel: LogLevel, indent: String, message: String)
237 
238     // TODO: This should be removed once MultiplexingWriter starts applying indent, at which point
239     // println() should be used instead.
240     fun write(cbuf: CharArray, off: Int, len: Int)
241 
242     fun flush()
243 }
244 
245 private class StreamPrinter(
246     override val logLevel: LogLevel,
247     val out: PrintWriter,
248 ) : LogPrinter {
249     override fun println(logLevel: LogLevel, indent: String, message: String) {
250         out.print(indent)
251         out.println(message)
252     }
253 
254     override fun write(cbuf: CharArray, off: Int, len: Int) {
255         out.write(cbuf, off, len)
256     }
257 
258     override fun flush() {
259         out.flush()
260     }
261 }
262