aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/logger
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/logger')
-rw-r--r--src/main/scala/logger/Logger.scala85
-rw-r--r--src/main/scala/logger/LoggerAnnotations.scala77
-rw-r--r--src/main/scala/logger/LoggerOptions.scala38
-rw-r--r--src/main/scala/logger/package.scala21
-rw-r--r--src/main/scala/logger/phases/AddDefaults.scala27
-rw-r--r--src/main/scala/logger/phases/Checks.scala42
6 files changed, 264 insertions, 26 deletions
diff --git a/src/main/scala/logger/Logger.scala b/src/main/scala/logger/Logger.scala
index 6e8dbeb1..1cf7d7ee 100644
--- a/src/main/scala/logger/Logger.scala
+++ b/src/main/scala/logger/Logger.scala
@@ -4,7 +4,12 @@ package logger
import java.io.{ByteArrayOutputStream, File, FileOutputStream, PrintStream}
-import firrtl.ExecutionOptionsManager
+import firrtl.{ExecutionOptionsManager, HasFirrtlOptions, AnnotationSeq}
+import firrtl.stage.FirrtlOptions
+import firrtl.options.StageOptions
+import firrtl.options.Viewer.view
+import firrtl.stage.FirrtlOptionsView
+import logger.phases.{AddDefaults, Checks}
import scala.util.DynamicVariable
@@ -120,24 +125,9 @@ object Logger {
* @tparam A The return type of codeBlock
* @return Whatever block returns
*/
- def makeScope[A](manager: ExecutionOptionsManager)(codeBlock: => A): A = {
- val runState: LoggerState = {
- val newRunState = updatableLoggerState.value.getOrElse(new LoggerState)
- if(newRunState.fromInvoke) {
- newRunState
- }
- else {
- val forcedNewRunState = new LoggerState
- forcedNewRunState.fromInvoke = true
- forcedNewRunState
- }
- }
-
- updatableLoggerState.withValue(Some(runState)) {
- setOptions(manager)
- codeBlock
- }
- }
+ @deprecated("Use makeScope(opts: FirrtlOptions)", "1.2")
+ def makeScope[A](manager: ExecutionOptionsManager)(codeBlock: => A): A =
+ makeScope(manager.commonOptions.toAnnotations)(codeBlock)
/**
* See makeScope using manager. This creates a manager from a command line arguments style
@@ -147,6 +137,7 @@ object Logger {
* @tparam A return type of codeBlock
* @return
*/
+ @deprecated("Use makescope(opts: FirrtlOptions)", "1.2")
def makeScope[A](args: Array[String] = Array.empty)(codeBlock: => A): A = {
val executionOptionsManager = new ExecutionOptionsManager("logger")
if(executionOptionsManager.parse(args)) {
@@ -157,6 +148,29 @@ object Logger {
}
}
+ /** Set a scope for this logger based on available annotations
+ * @param options a sequence annotations
+ * @param codeBlock some Scala code over which to define this scope
+ * @tparam A return type of the code block
+ * @return the original return of the code block
+ */
+ def makeScope[A](options: AnnotationSeq)(codeBlock: => A): A = {
+ val runState: LoggerState = {
+ val newRunState = updatableLoggerState.value.getOrElse(new LoggerState)
+ if(newRunState.fromInvoke) {
+ newRunState
+ }
+ else {
+ val forcedNewRunState = new LoggerState
+ forcedNewRunState.fromInvoke = true
+ forcedNewRunState
+ }
+ }
+ updatableLoggerState.withValue(Some(runState)) {
+ setOptions(options)
+ codeBlock
+ }
+ }
/**
* Used to test whether a given log statement should generate some logging output.
@@ -341,20 +355,33 @@ object Logger {
* from the command line via an options manager
* @param optionsManager manager
*/
- def setOptions(optionsManager: ExecutionOptionsManager): Unit = {
- val commonOptions = optionsManager.commonOptions
- state.globalLevel = (state.globalLevel, commonOptions.globalLogLevel) match {
+ @deprecated("Use setOptions(annotations: AnnotationSeq)", "1.2")
+ def setOptions(optionsManager: ExecutionOptionsManager): Unit =
+ setOptions(optionsManager.commonOptions.toAnnotations)
+
+ /** Set logger options based on the content of an [[firrtl.AnnotationSeq AnnotationSeq]]
+ * @param inputAnnotations annotation sequence containing logger options
+ */
+ def setOptions(inputAnnotations: AnnotationSeq): Unit = {
+ val annotations = Seq( AddDefaults,
+ Checks )
+ .foldLeft(inputAnnotations)((a, p) => p.runTransform(a))
+
+ val lopts = view[LoggerOptions](annotations)
+ state.globalLevel = (state.globalLevel, lopts.globalLogLevel) match {
case (LogLevel.None, LogLevel.None) => LogLevel.None
case (x, LogLevel.None) => x
case (LogLevel.None, x) => x
case (_, x) => x
case _ => LogLevel.Error
}
- setClassLogLevels(commonOptions.classLogLevels)
- if(commonOptions.logToFile) {
- setOutput(commonOptions.getLogFileName(optionsManager))
+ setClassLogLevels(lopts.classLogLevels)
+
+ if (lopts.logFileName.nonEmpty) {
+ setOutput(lopts.logFileName.get)
}
- state.logClassNames = commonOptions.logClassNames
+
+ state.logClassNames = lopts.logClassNames
}
}
@@ -399,3 +426,9 @@ class Logger(containerClass: String) {
Logger.showMessage(LogLevel.Trace, containerClass, message)
}
}
+
+/** An exception originating from the Logger
+ * @param str an exception message
+ * @param cause a reason for the exception
+ */
+class LoggerException(val str: String, cause: Throwable = null) extends RuntimeException(str, cause)
diff --git a/src/main/scala/logger/LoggerAnnotations.scala b/src/main/scala/logger/LoggerAnnotations.scala
new file mode 100644
index 00000000..2811cc6c
--- /dev/null
+++ b/src/main/scala/logger/LoggerAnnotations.scala
@@ -0,0 +1,77 @@
+// See LICENSE for license details.
+
+package logger
+
+import firrtl.AnnotationSeq
+import firrtl.annotations.{Annotation, NoTargetAnnotation}
+import firrtl.options.{HasScoptOptions, StageUtils}
+
+import scopt.OptionParser
+
+/** An annotation associated with a Logger command line option */
+sealed trait LoggerOption { this: Annotation => }
+
+/** Describes the verbosity of information to log
+ * - set with `-ll/--log-level`
+ * - if unset, a [[LogLevelAnnotation]] with the default log level will be emitted
+ * @param level the level of logging
+ */
+case class LogLevelAnnotation(globalLogLevel: LogLevel.Value = LogLevel.None) extends NoTargetAnnotation with LoggerOption
+
+object LogLevelAnnotation extends HasScoptOptions {
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("log-level")
+ .abbr("ll")
+ .valueName("<Error|Warn|Info|Debug|Trace>")
+ .action( (x, c) => LogLevelAnnotation(LogLevel(x)) +: c )
+ .validate{ x =>
+ lazy val msg = s"$x bad value must be one of error|warn|info|debug|trace"
+ if (Array("error", "warn", "info", "debug", "trace").contains(x.toLowerCase)) { p.success }
+ else { p.failure(msg) }}
+ .unbounded()
+ .text(s"Sets the verbosity level of logging, default is ${new LoggerOptions().globalLogLevel}")
+}
+
+/** Describes a mapping of a class to a specific log level
+ * - set with `-cll/--class-log-level`
+ * @param name the class name to log
+ * @param level the verbosity level
+ */
+case class ClassLogLevelAnnotation(className: String, level: LogLevel.Value) extends NoTargetAnnotation with LoggerOption
+
+object ClassLogLevelAnnotation extends HasScoptOptions {
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[Seq[String]]("class-log-level")
+ .abbr("cll")
+ .valueName("<FullClassName:[Error|Warn|Info|Debug|Trace]>[,...]")
+ .action( (x, c) => (x.map { y =>
+ val className :: levelName :: _ = y.split(":").toList
+ val level = LogLevel(levelName)
+ ClassLogLevelAnnotation(className, level) }) ++ c )
+ .unbounded()
+ .text(s"This defines per-class verbosity of logging")
+}
+
+/** Enables logging to a file (as opposed to STDOUT)
+ * - maps to [[LoggerOptions.logFileName]]
+ * - enabled with `--log-file`
+ */
+case class LogFileAnnotation(file: Option[String]) extends NoTargetAnnotation with LoggerOption
+
+object LogFileAnnotation extends HasScoptOptions {
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = {
+ p.opt[String]("log-file")
+ .action( (x, c) => LogFileAnnotation(Some(x)) +: c )
+ .unbounded()
+ .text(s"log to the specified file")
+ }
+}
+
+/** Enables class names in log output
+ * - enabled with `-lcn/--log-class-names`
+ */
+case object LogClassNamesAnnotation extends NoTargetAnnotation with LoggerOption with HasScoptOptions {
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[Unit]("log-class-names")
+ .abbr("lcn")
+ .action( (x, c) => LogClassNamesAnnotation +: c )
+ .unbounded()
+ .text(s"shows class names and log level in logging output, useful for target --class-log-level")
+}
diff --git a/src/main/scala/logger/LoggerOptions.scala b/src/main/scala/logger/LoggerOptions.scala
new file mode 100644
index 00000000..299382f0
--- /dev/null
+++ b/src/main/scala/logger/LoggerOptions.scala
@@ -0,0 +1,38 @@
+// See LICENSE for license details.
+
+package logger
+
+/** Internal options used to control the logging in programs that are part of the Chisel stack
+ *
+ * @param globalLogLevel the verbosity of logging (default: [[logger.LogLevel.None]])
+ * @param classLogLevels the individual verbosity of logging for specific classes
+ * @param logToFile if true, log to a file
+ * @param logClassNames indicates logging verbosity on a class-by-class basis
+ */
+class LoggerOptions private [logger] (
+ val globalLogLevel: LogLevel.Value = LogLevelAnnotation().globalLogLevel,
+ val classLogLevels: Map[String, LogLevel.Value] = Map.empty,
+ val logClassNames: Boolean = false,
+ val logFileName: Option[String] = None) {
+
+ private [logger] def copy(
+ globalLogLevel: LogLevel.Value = globalLogLevel,
+ classLogLevels: Map[String, LogLevel.Value] = classLogLevels,
+ logClassNames: Boolean = logClassNames,
+ logFileName: Option[String] = logFileName): LoggerOptions = {
+
+ new LoggerOptions(
+ globalLogLevel = globalLogLevel,
+ classLogLevels = classLogLevels,
+ logClassNames = logClassNames,
+ logFileName = logFileName)
+
+ }
+
+ /** Return the name of the log file, defaults to `a.log` if unspecified */
+ def getLogFileName(): Option[String] = if (!logToFile) None else logFileName.orElse(Some("a.log"))
+
+ /** True if a [[Logger]] should be writing to a file */
+ @deprecated("logToFile was removed, use logFileName.nonEmpty", "1.2")
+ def logToFile(): Boolean = logFileName.nonEmpty
+}
diff --git a/src/main/scala/logger/package.scala b/src/main/scala/logger/package.scala
new file mode 100644
index 00000000..52a3331a
--- /dev/null
+++ b/src/main/scala/logger/package.scala
@@ -0,0 +1,21 @@
+// See LICENSE for license details.
+
+import firrtl.AnnotationSeq
+import firrtl.options.OptionsView
+
+package object logger {
+
+ implicit object LoggerOptionsView extends OptionsView[LoggerOptions] {
+ def view(options: AnnotationSeq): LoggerOptions = options
+ .foldLeft(new LoggerOptions()) { (c, x) =>
+ x match {
+ case LogLevelAnnotation(logLevel) => c.copy(globalLogLevel = logLevel)
+ case ClassLogLevelAnnotation(name, level) => c.copy(classLogLevels = c.classLogLevels + (name -> level))
+ case LogFileAnnotation(f) => c.copy(logFileName = f)
+ case LogClassNamesAnnotation => c.copy(logClassNames = true)
+ case _ => c
+ }
+ }
+ }
+
+}
diff --git a/src/main/scala/logger/phases/AddDefaults.scala b/src/main/scala/logger/phases/AddDefaults.scala
new file mode 100644
index 00000000..f6daa811
--- /dev/null
+++ b/src/main/scala/logger/phases/AddDefaults.scala
@@ -0,0 +1,27 @@
+// See LICENSE for license details.
+
+package logger.phases
+
+import firrtl.AnnotationSeq
+import firrtl.options.Phase
+
+import logger.{LoggerOption, LogLevelAnnotation}
+
+/** Add default logger [[Annotation]]s */
+private [logger] object AddDefaults extends Phase {
+
+ /** Add missing default [[Logger]] [[Annotation]]s to an [[AnnotationSeq]]
+ * @param annotations input annotations
+ * @return output annotations with defaults
+ */
+ def transform(annotations: AnnotationSeq): AnnotationSeq = {
+ var ll = true
+ annotations.collect{ case a: LoggerOption => a }.map{
+ case _: LogLevelAnnotation => ll = false
+ case _ =>
+ }
+ annotations ++
+ (if (ll) Seq(LogLevelAnnotation()) else Seq() )
+ }
+
+}
diff --git a/src/main/scala/logger/phases/Checks.scala b/src/main/scala/logger/phases/Checks.scala
new file mode 100644
index 00000000..c706948c
--- /dev/null
+++ b/src/main/scala/logger/phases/Checks.scala
@@ -0,0 +1,42 @@
+// See LICENSE for license details.
+
+package logger.phases
+
+import firrtl.AnnotationSeq
+import firrtl.annotations.Annotation
+import firrtl.options.Phase
+
+import logger.{LogLevelAnnotation, LogFileAnnotation, LoggerException}
+
+import scala.collection.mutable
+
+/** Check that an [[firrtl.AnnotationSeq AnnotationSeq]] has all necessary [[firrtl.annotations.Annotation Annotation]]s
+ * for a [[Logger]] */
+object Checks extends Phase {
+
+ /** Ensure that an [[firrtl.AnnotationSeq AnnotationSeq]] has necessary [[Logger]] [[firrtl.annotations.Annotation
+ * Annotation]]s
+ * @param annotations input annotations
+ * @return input annotations unmodified
+ * @throws logger.LoggerException
+ */
+ def transform(annotations: AnnotationSeq): AnnotationSeq = {
+ val ll, lf = mutable.ListBuffer[Annotation]()
+ annotations.foreach(
+ _ match {
+ case a: LogLevelAnnotation => ll += a
+ case a: LogFileAnnotation => lf += a
+ case _ => })
+ if (ll.size > 1) {
+ val l = ll.map{ case LogLevelAnnotation(x) => x }
+ throw new LoggerException(
+ s"""|At most one log level can be specified, but found '${l.mkString(", ")}' specified via:
+ | - an option or annotation: -ll, --log-level, LogLevelAnnotation""".stripMargin )}
+ if (lf.size > 1) {
+ throw new LoggerException(
+ s"""|At most one log file can be specified, but found ${lf.size} combinations of:
+ | - an options or annotation: -ltf, --log-to-file, --log-file, LogFileAnnotation""".stripMargin )}
+ annotations
+ }
+
+}