From 8e6fe02119c48c271c10169e32e2143edde25388 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 24 Feb 2026 14:23:09 -0800 Subject: [PATCH 1/3] Log warnings and errors to stderr instead of stdout --- firrtl/src/main/scala/logger/Logger.scala | 43 +++++++++++++++++-- .../circtTests/stage/ChiselStageSpec.scala | 8 ++-- src/test/scala/chiselTests/LogUtils.scala | 3 +- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/firrtl/src/main/scala/logger/Logger.scala b/firrtl/src/main/scala/logger/Logger.scala index ec6b80ed2d7..b11970fd38d 100644 --- a/firrtl/src/main/scala/logger/Logger.scala +++ b/firrtl/src/main/scala/logger/Logger.scala @@ -63,6 +63,7 @@ private class LoggerState { val classToLevelCache = new scala.collection.mutable.HashMap[String, LogLevel.Value] var logClassNames = false var stream: PrintStream = Console.out + var errorStream: PrintStream = Console.err var fromInvoke: Boolean = false // this is used to not have invokes re-create run-state var stringBufferOption: Option[Logger.OutputCaptor] = None @@ -79,6 +80,7 @@ private class LoggerState { newState.globalLevel = this.globalLevel newState.classLevels ++= this.classLevels newState.stream = this.stream + newState.errorStream = this.errorStream newState.logClassNames = this.logClassNames newState } @@ -211,10 +213,15 @@ object Logger { */ private def showMessage(level: LogLevel.Value, className: String, message: => String): Unit = { def logIt(): Unit = { + // Use errorStream for Error and Warn levels, stream for others + val stream = level match { + case LogLevel.Error | LogLevel.Warn => state.errorStream + case _ => state.stream + } if (state.logClassNames) { - state.stream.println(s"[$level:$className] $message") + stream.println(s"[$level:$className] $message") } else { - state.stream.println(message) + stream.println(message) } } testPackageNameMatch(className, level) match { @@ -243,6 +250,7 @@ object Logger { state.logClassNames = false state.globalLevel = LogLevel.Error state.stream = System.out + state.errorStream = Console.err } /** @@ -302,24 +310,53 @@ object Logger { /** * Set the logging destination to a file name * @param fileName destination name + * @deprecated Use setStandardOutput and setErrorOutput instead. This method sets both standard and error streams to the same file. */ + @deprecated( + "Use setStandardOutput and setErrorOutput instead. This method sets both standard and error streams to the same file.", + "Chisel 7.10.0" + ) def setOutput(fileName: String): Unit = { state.stream = new PrintStream(new FileOutputStream(new File(fileName))) + state.errorStream = new PrintStream(new FileOutputStream(new File(fileName))) } /** * Set the logging destination to a print stream * @param stream destination stream + * @deprecated Use setStandardOutput and setErrorOutput instead. This method sets both standard and error streams to the same stream. */ + @deprecated( + "Use setStandardOutput and setErrorOutput instead. This method sets both standard and error streams to the same stream.", + "Chisel 7.10.0" + ) def setOutput(stream: PrintStream): Unit = { state.stream = stream + state.errorStream = stream + } + + /** + * Set the standard logging destination to a print stream (for Info, Debug, Trace levels) + * @param stream destination stream for standard output + */ + def setStandardOutput(stream: PrintStream): Unit = { + state.stream = stream + } + + /** + * Set the error logging destination to a print stream (for Error and Warn levels) + * @param stream destination stream for errors and warnings + */ + def setErrorOutput(stream: PrintStream): Unit = { + state.errorStream = stream } /** - * Sets the logging destination to Console.out + * Sets the logging destination to Console.out and Console.err */ def setConsole(): Unit = { state.stream = Console.out + state.errorStream = Console.err } /** diff --git a/src/test/scala-2/circtTests/stage/ChiselStageSpec.scala b/src/test/scala-2/circtTests/stage/ChiselStageSpec.scala index 5395f86536f..ccd39b8e098 100644 --- a/src/test/scala-2/circtTests/stage/ChiselStageSpec.scala +++ b/src/test/scala-2/circtTests/stage/ChiselStageSpec.scala @@ -615,7 +615,7 @@ class ChiselStageSpec extends AnyFunSpec with Matchers with chiselTests.LogUtils } } - val lines = stdout.split("\n") + val lines = stderr.split("\n") // Fuzzy includes aren't ideal but there is ANSI color in these strings that is hard to match lines(0) should include( "src/test/scala-2/circtTests/stage/ChiselStageSpec.scala 122:9: Negative shift amounts are illegal (got -1)" @@ -635,7 +635,7 @@ class ChiselStageSpec extends AnyFunSpec with Matchers with chiselTests.LogUtils } } - val lines = stdout.split("\n") + val lines = stderr.split("\n") // Fuzzy includes aren't ideal but there is ANSI color in these strings that is hard to match lines.size should equal(2) lines(0) should include( @@ -662,7 +662,7 @@ class ChiselStageSpec extends AnyFunSpec with Matchers with chiselTests.LogUtils } } - val lines = stdout.split("\n") + val lines = stderr.split("\n") // Fuzzy includes aren't ideal but there is ANSI color in these strings that is hard to match lines(0) should include("Foo 3:10: Negative shift amounts are illegal (got -1)") lines(1) should include("I am the file in sourceroot1") @@ -687,7 +687,7 @@ class ChiselStageSpec extends AnyFunSpec with Matchers with chiselTests.LogUtils } } - val lines = stdout.split("\n") + val lines = stderr.split("\n") // Fuzzy includes aren't ideal but there is ANSI color in these strings that is hard to match lines(0) should include("Foo 3:10: Negative shift amounts are illegal (got -1)") lines(1) should include("I am the file in sourceroot2") diff --git a/src/test/scala/chiselTests/LogUtils.scala b/src/test/scala/chiselTests/LogUtils.scala index e87f475c1ae..bf0eb5bb294 100644 --- a/src/test/scala/chiselTests/LogUtils.scala +++ b/src/test/scala/chiselTests/LogUtils.scala @@ -33,7 +33,8 @@ trait LogUtils { val baos = new ByteArrayOutputStream() val stream = new PrintStream(baos, true, "utf-8") val ret = Logger.makeScope(LogLevelAnnotation(level) :: Nil) { - Logger.setOutput(stream) + Logger.setStandardOutput(stream) + Logger.setErrorOutput(stream) thunk } (baos.toString, ret) From 83b1f7c8cc56885f6b3dacc7808feb70bfec7838 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 24 Feb 2026 14:39:06 -0800 Subject: [PATCH 2/3] Update tests in firrtl --- .../UnrecognizedAnnotationSpec.scala | 3 +- .../test/scala/loggertests/LoggerSpec.scala | 45 ++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/firrtl/src/test/scala/firrtlTests/annotationTests/UnrecognizedAnnotationSpec.scala b/firrtl/src/test/scala/firrtlTests/annotationTests/UnrecognizedAnnotationSpec.scala index 7847b025bef..3ec52d31690 100644 --- a/firrtl/src/test/scala/firrtlTests/annotationTests/UnrecognizedAnnotationSpec.scala +++ b/firrtl/src/test/scala/firrtlTests/annotationTests/UnrecognizedAnnotationSpec.scala @@ -36,7 +36,8 @@ class UnrecognizedAnnotationSpec extends FirrtlFlatSpec { // Default log level is error, which the JSON parsing uses here Logger.makeScope(Seq()) { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) val parsingError = intercept[UnrecogizedAnnotationsException] { JsonProtocol.deserialize( diff --git a/firrtl/src/test/scala/loggertests/LoggerSpec.scala b/firrtl/src/test/scala/loggertests/LoggerSpec.scala index e0a7b43dba4..b819e8301bd 100644 --- a/firrtl/src/test/scala/loggertests/LoggerSpec.scala +++ b/firrtl/src/test/scala/loggertests/LoggerSpec.scala @@ -44,7 +44,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "setting level to None will result in warn messages" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.None) val r1 = new Logger1 @@ -62,7 +63,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "only warn and error shows up by default" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) val r1 = new Logger1 r1.run() @@ -79,7 +81,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "setting level to warn will result in error and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Warn) val r1 = new Logger1 @@ -95,7 +98,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "setting level to info will result in error, info, and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Info) val r1 = new Logger1 @@ -111,10 +115,12 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "setting level to debug will result in error, info, debug, and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Error) - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Debug) val r1 = new Logger1 @@ -131,10 +137,12 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "setting level to trace will result in error, info, debug, trace, and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Error) - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Trace) val r1 = new Logger1 @@ -154,7 +162,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "capture logging from LogsInfo2" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel("loggertests.LogsInfo2", LogLevel.Info) @@ -172,7 +181,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "capture logging from LogsInfo2 using class" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(classOf[LogsInfo2], LogLevel.Info) @@ -190,7 +200,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "capture logging from LogsInfo3" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel("loggertests.LogsInfo3", LogLevel.Info) @@ -210,7 +221,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "both log because of package, also showing re-run after change works" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Error) Logger.setLevel("loggertests", LogLevel.Error) @@ -240,7 +252,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "check for false positives" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel("bad-loggertests", LogLevel.Info) @@ -258,7 +271,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "show that class specific level supercedes global level" in { Logger.makeScope() { val captor = new OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) Logger.setLevel(LogLevel.Info) Logger.setLevel("loggertests.LogsInfo2", LogLevel.Error) @@ -277,7 +291,8 @@ class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with "Show that printstream remains across makeScopes" in { Logger.makeScope() { val captor = new Logger.OutputCaptor - Logger.setOutput(captor.printStream) + Logger.setStandardOutput(captor.printStream) + Logger.setErrorOutput(captor.printStream) logger.error("message 1") Logger.makeScope() { From d415f2455d9a835dad494d9f26b8541bb58200e8 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 24 Feb 2026 14:40:50 -0800 Subject: [PATCH 3/3] Update lit test --- lit/tests/ErrorCarat.sc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lit/tests/ErrorCarat.sc b/lit/tests/ErrorCarat.sc index 0db28b6ae4a..298106ec00e 100644 --- a/lit/tests/ErrorCarat.sc +++ b/lit/tests/ErrorCarat.sc @@ -1,4 +1,4 @@ -// RUN: JDK_JAVA_OPTIONS='-Dchisel.project.root=' not scala-cli --server=false --java-home=%JAVAHOME --extra-jars=%RUNCLASSPATH --scala-version=%SCALAVERSION --scala-option="-Xplugin:%SCALAPLUGINJARS" %s | FileCheck %s +// RUN: JDK_JAVA_OPTIONS='-Dchisel.project.root=' not scala-cli --server=false --java-home=%JAVAHOME --extra-jars=%RUNCLASSPATH --scala-version=%SCALAVERSION --scala-option="-Xplugin:%SCALAPLUGINJARS" %s 2>&1 | FileCheck %s // SPDX-License-Identifier: Apache-2.0 import chisel3._