diff options
Diffstat (limited to 'src/main/scala/logger')
| -rw-r--r-- | src/main/scala/logger/Logger.scala | 85 | ||||
| -rw-r--r-- | src/main/scala/logger/LoggerAnnotations.scala | 77 | ||||
| -rw-r--r-- | src/main/scala/logger/LoggerOptions.scala | 38 | ||||
| -rw-r--r-- | src/main/scala/logger/package.scala | 21 | ||||
| -rw-r--r-- | src/main/scala/logger/phases/AddDefaults.scala | 27 | ||||
| -rw-r--r-- | src/main/scala/logger/phases/Checks.scala | 42 |
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 + } + +} |
