HostStubGen: Write verbose/debug log to a file

See below for the log filename.

Also disable an unimplemented flag.

Bug: 311174191
Test: ./scripts/run-all-tests.sh
Test: m framework-minus-apex.ravenwood
    and examine console output and
    out/soong/.intermediates/frameworks/base/framework-minus-apex.ravenwood-base/android_common/gen/hoststubgen_framework-minus-apex.log
Change-Id: I9997370c93e2fe90276d5f3e657d45d440ca0a59
diff --git a/Ravenwood.bp b/Ravenwood.bp
index ca73378..68095d4 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -32,6 +32,8 @@
     cmd: "$(location hoststubgen) " +
         "@$(location ravenwood/ravenwood-standard-options.txt) " +
 
+        "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
+
         "--out-impl-jar $(location ravenwood.jar) " +
 
         "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
@@ -52,6 +54,8 @@
         // Following files are created just as FYI.
         "hoststubgen_keep_all.txt",
         "hoststubgen_dump.txt",
+
+        "hoststubgen_framework-minus-apex.log",
     ],
     visibility: ["//visibility:private"],
 }
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt
index 8ad21fa..f64f26d 100644
--- a/ravenwood/ravenwood-standard-options.txt
+++ b/ravenwood/ravenwood-standard-options.txt
@@ -1,6 +1,6 @@
 # File containing standard options to HostStubGen for Ravenwood
 
---debug
+# --debug # To enable debug log on consone
 
 # Keep all classes / methods / fields, but make the methods throw.
 --default-throw
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
index 91e6814..d97dd7c 100755
--- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -235,10 +235,6 @@
     "Duplicate or conflicting argument found: --in-jar" \
     ""
 
-EXTRA_ARGS="--quiet" run_hoststubgen_for_failure "Conflicting arg" \
-    "Duplicate or conflicting argument found: --quiet" \
-    ""
-
 
 echo "All tests passed"
 exit 0
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index dbcf3a5..4e0cd09 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -239,7 +239,7 @@
             errors: HostStubGenErrors,
             ) {
         log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
-        log.i("Checker is %s", if (enableChecker) "enabled" else "disabled")
+        log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
 
         val start = System.currentTimeMillis()
 
@@ -264,7 +264,7 @@
             }
         }
         val end = System.currentTimeMillis()
-        log.v("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
+        log.i("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
     }
 
     private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index 5e71a36..18065ba 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -15,10 +15,12 @@
  */
 package com.android.hoststubgen
 
-import java.io.OutputStream
-import java.io.PrintStream
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.io.PrintWriter
+import java.io.Writer
 
-val log: HostStubGenLogger = HostStubGenLogger()
+val log: HostStubGenLogger = HostStubGenLogger().setConsoleLogLevel(LogLevel.Info)
 
 /** Logging level */
 enum class LogLevel {
@@ -30,15 +32,13 @@
     Debug,
 }
 
-/** Simple logging class. */
-class HostStubGenLogger(
-        private var out: PrintStream = System.out!!,
-        var level: LogLevel = LogLevel.Info,
-) {
-    companion object {
-        private val sNullPrintStream: PrintStream = PrintStream(OutputStream.nullOutputStream())
-    }
-
+/**
+ * Simple logging class.
+ *
+ * By default, it has no printers set. Use [setConsoleLogLevel] or [addFilePrinter] to actually
+ * write log.
+ */
+class HostStubGenLogger {
     private var indentLevel: Int = 0
         get() = field
         set(value) {
@@ -47,6 +47,56 @@
         }
     private var indent: String = ""
 
+    private val printers: MutableList<LogPrinter> = mutableListOf()
+
+    private var consolePrinter: LogPrinter? = null
+
+    private var maxLogLevel = LogLevel.None
+
+    private fun updateMaxLogLevel() {
+        maxLogLevel = LogLevel.None
+
+        printers.forEach {
+            if (maxLogLevel < it.logLevel) {
+                maxLogLevel = it.logLevel
+            }
+        }
+    }
+
+    private fun addPrinter(printer: LogPrinter) {
+        printers.add(printer)
+        updateMaxLogLevel()
+    }
+
+    private fun removePrinter(printer: LogPrinter) {
+        printers.remove(printer)
+        updateMaxLogLevel()
+    }
+
+    fun setConsoleLogLevel(level: LogLevel): HostStubGenLogger {
+        // If there's already a console log printer set, remove it, and then add a new one
+        consolePrinter?.let {
+            removePrinter(it)
+        }
+        val cp = StreamPrinter(level, PrintWriter(System.out))
+        addPrinter(cp)
+        consolePrinter = cp
+
+        return this
+    }
+
+    fun addFilePrinter(level: LogLevel, logFilename: String): HostStubGenLogger {
+        addPrinter(StreamPrinter(level, PrintWriter(BufferedOutputStream(
+            FileOutputStream(logFilename)))))
+
+        return this
+    }
+
+    /** Flush all the printers */
+    fun flush() {
+        printers.forEach { it.flush() }
+    }
+
     fun indent() {
         indentLevel++
     }
@@ -68,92 +118,71 @@
     }
 
     fun isEnabled(level: LogLevel): Boolean {
-        return level.ordinal <= this.level.ordinal
+        return level.ordinal <= maxLogLevel.ordinal
     }
 
-    private fun println(message: String) {
-        out.print(indent)
-        out.println(message)
+    private fun println(level: LogLevel, message: String) {
+        printers.forEach {
+            if (it.logLevel.ordinal >= level.ordinal) {
+                it.println(level, indent, message)
+            }
+        }
+    }
+
+    private fun println(level: LogLevel, format: String, vararg args: Any?) {
+        if (isEnabled(level)) {
+            println(level, String.format(format, *args))
+        }
     }
 
     /** Log an error. */
     fun e(message: String) {
-        if (level.ordinal < LogLevel.Error.ordinal) {
-            return
-        }
-        println(message)
+        println(LogLevel.Error, message)
     }
 
     /** Log an error. */
     fun e(format: String, vararg args: Any?) {
-        if (level.ordinal < LogLevel.Error.ordinal) {
-            return
-        }
-        e(String.format(format, *args))
+        println(LogLevel.Error, format, *args)
     }
 
     /** Log a warning. */
     fun w(message: String) {
-        if (level.ordinal < LogLevel.Warn.ordinal) {
-            return
-        }
-        println(message)
+        println(LogLevel.Warn, message)
     }
 
     /** Log a warning. */
     fun w(format: String, vararg args: Any?) {
-        if (level.ordinal < LogLevel.Warn.ordinal) {
-            return
-        }
-        w(String.format(format, *args))
+        println(LogLevel.Warn, format, *args)
     }
 
     /** Log an info message. */
     fun i(message: String) {
-        if (level.ordinal < LogLevel.Info.ordinal) {
-            return
-        }
-        println(message)
+        println(LogLevel.Info, message)
     }
 
-    /** Log a debug message. */
+    /** Log an info message. */
     fun i(format: String, vararg args: Any?) {
-        if (level.ordinal < LogLevel.Warn.ordinal) {
-            return
-        }
-        i(String.format(format, *args))
+        println(LogLevel.Info, format, *args)
     }
 
     /** Log a verbose message. */
     fun v(message: String) {
-        if (level.ordinal < LogLevel.Verbose.ordinal) {
-            return
-        }
-        println(message)
+        println(LogLevel.Verbose, message)
     }
 
     /** Log a verbose message. */
     fun v(format: String, vararg args: Any?) {
-        if (level.ordinal < LogLevel.Verbose.ordinal) {
-            return
-        }
-        v(String.format(format, *args))
+        println(LogLevel.Verbose, format, *args)
     }
 
     /** Log a debug message. */
     fun d(message: String) {
-        if (level.ordinal < LogLevel.Debug.ordinal) {
-            return
-        }
-        println(message)
+        println(LogLevel.Debug, message)
     }
 
     /** Log a debug message. */
     fun d(format: String, vararg args: Any?) {
-        if (level.ordinal < LogLevel.Warn.ordinal) {
-            return
-        }
-        d(String.format(format, *args))
+        println(LogLevel.Debug, format, *args)
     }
 
     inline fun forVerbose(block: () -> Unit) {
@@ -168,31 +197,65 @@
         }
     }
 
-    /** Return a stream for error. */
-    fun getErrorPrintStream(): PrintStream {
-        if (level.ordinal < LogLevel.Error.ordinal) {
-            return sNullPrintStream
-        }
-
-        // TODO Apply indent
-        return PrintStream(out)
+    /** Return a Writer for a given log level. */
+    fun getWriter(level: LogLevel): Writer {
+        return MultiplexingWriter(level)
     }
 
-    /** Return a stream for verbose messages. */
-    fun getVerbosePrintStream(): PrintStream {
-        if (level.ordinal < LogLevel.Verbose.ordinal) {
-            return sNullPrintStream
+    private inner class MultiplexingWriter(val level: LogLevel) : Writer() {
+        private inline fun forPrinters(callback: (LogPrinter) -> Unit) {
+            printers.forEach {
+                if (it.logLevel.ordinal >= level.ordinal) {
+                    callback(it)
+                }
+            }
         }
-        // TODO Apply indent
-        return PrintStream(out)
+
+        override fun close() {
+            flush()
+        }
+
+        override fun flush() {
+            forPrinters {
+                it.flush()
+            }
+        }
+
+        override fun write(cbuf: CharArray, off: Int, len: Int) {
+            // TODO Apply indent
+            forPrinters {
+                it.write(cbuf, off, len)
+            }
+        }
+    }
+}
+
+private interface LogPrinter {
+    val logLevel: LogLevel
+
+    fun println(logLevel: LogLevel, indent: String, message: String)
+
+    // TODO: This should be removed once MultiplexingWriter starts applying indent, at which point
+    // println() should be used instead.
+    fun write(cbuf: CharArray, off: Int, len: Int)
+
+    fun flush()
+}
+
+private class StreamPrinter(
+    override val logLevel: LogLevel,
+    val out: PrintWriter,
+) : LogPrinter {
+    override fun println(logLevel: LogLevel, indent: String, message: String) {
+        out.print(indent)
+        out.println(message)
     }
 
-    /** Return a stream for debug messages. */
-    fun getInfoPrintStream(): PrintStream {
-        if (level.ordinal < LogLevel.Info.ordinal) {
-            return sNullPrintStream
-        }
-        // TODO Apply indent
-        return PrintStream(out)
+    override fun write(cbuf: CharArray, off: Int, len: Int) {
+        out.write(cbuf, off, len)
     }
-}
\ No newline at end of file
+
+    override fun flush() {
+        out.flush()
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 0ae52af..d2ead18 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -101,9 +101,7 @@
 
         var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove),
 
-        var logLevel: SetOnce<LogLevel> = SetOnce(LogLevel.Info),
-
-        var cleanUpOnError: SetOnce<Boolean> = SetOnce(true),
+        var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
 
         var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
         var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
@@ -143,6 +141,11 @@
                 return name
             }
 
+            fun setLogFile(level: LogLevel, filename: String) {
+                log.addFilePrinter(level, filename)
+                log.i("$level log file: $filename")
+            }
+
             while (true) {
                 val arg = ai.nextArgOptional()
                 if (arg == null) {
@@ -161,9 +164,9 @@
                         // TODO: Write help
                         "-h", "--help" -> TODO("Help is not implemented yet")
 
-                        "-v", "--verbose" -> ret.logLevel.set(LogLevel.Verbose)
-                        "-d", "--debug" -> ret.logLevel.set(LogLevel.Debug)
-                        "-q", "--quiet" -> ret.logLevel.set(LogLevel.None)
+                        "-v", "--verbose" -> log.setConsoleLogLevel(LogLevel.Verbose)
+                        "-d", "--debug" -> log.setConsoleLogLevel(LogLevel.Debug)
+                        "-q", "--quiet" -> log.setConsoleLogLevel(LogLevel.None)
 
                         "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists()
                         "--out-stub-jar" -> ret.outStubJar.setNextStringArg()
@@ -211,7 +214,7 @@
                             ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg()
 
                         "--package-redirect" ->
-                            ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg))
+                            ret.packageRedirects += parsePackageRedirect(nextArg())
 
                         "--annotation-allowed-classes-file" ->
                             ret.annotationAllowedClassesFile.setNextStringArg()
@@ -246,13 +249,15 @@
 
                         "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg()
 
+                        "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg())
+                        "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())
+
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
                 } catch (e: SetOnce.SetMoreThanOnceException) {
                     throw ArgumentsException("Duplicate or conflicting argument found: $arg")
                 }
             }
-            log.w(ret.toString())
 
             if (!ret.inJar.isSet) {
                 throw ArgumentsException("Required option missing: --in-jar")
@@ -377,7 +382,6 @@
               intersectStubJars=$intersectStubJars,
               policyOverrideFile=$policyOverrideFile,
               defaultPolicy=$defaultPolicy,
-              logLevel=$logLevel,
               cleanUpOnError=$cleanUpOnError,
               enableClassChecker=$enableClassChecker,
               enablePreTrace=$enablePreTrace,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
index 38ba0cc..4882c00 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
@@ -17,6 +17,8 @@
 
 package com.android.hoststubgen
 
+import java.io.PrintWriter
+
 const val COMMAND_NAME = "HostStubGen"
 
 /**
@@ -25,13 +27,12 @@
 fun main(args: Array<String>) {
     var success = false
     var clanupOnError = false
+
     try {
         // Parse the command line arguments.
         val options = HostStubGenOptions.parseArgs(args)
         clanupOnError = options.cleanUpOnError.get
 
-        log.level = options.logLevel.get
-
         log.v("HostStubGen started")
         log.v("Options: $options")
 
@@ -39,17 +40,18 @@
         HostStubGen(options).run()
 
         success = true
-    } catch (e: Exception) {
+    } catch (e: Throwable) {
         log.e("$COMMAND_NAME: Error: ${e.message}")
         if (e !is UserErrorException) {
-            e.printStackTrace(log.getErrorPrintStream())
+            e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
         }
         if (clanupOnError) {
-            TODO("clanupOnError is not implemented yet")
+            TODO("Remove output jars here")
         }
+    } finally {
+        log.i("$COMMAND_NAME finished")
+        log.flush()
     }
 
-    log.v("HostStubGen finished")
-
     System.exit(if (success) 0 else 1 )
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index f25e862..96e4a3f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -16,6 +16,7 @@
 package com.android.hoststubgen.visitors
 
 import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.LogLevel
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.getPackageNameFromClassName
 import com.android.hoststubgen.asm.resolveClassName
@@ -229,7 +230,7 @@
         ): ClassVisitor {
             var next = nextVisitor
 
-            val verbosePrinter = PrintWriter(log.getVerbosePrintStream())
+            val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose))
 
             // Inject TraceClassVisitor for debugging.
             if (options.enablePostTrace) {