diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main/scala/firrtl/Driver.scala | 4 | ||||
| -rw-r--r-- | src/main/scala/firrtl/ExecutionOptionsManager.scala | 80 | ||||
| -rw-r--r-- | src/main/scala/firrtl/LoweringCompilers.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/logger/Logger.scala | 101 |
4 files changed, 182 insertions, 5 deletions
diff --git a/src/main/scala/firrtl/Driver.scala b/src/main/scala/firrtl/Driver.scala index 5dbbf16a..ba5527b4 100644 --- a/src/main/scala/firrtl/Driver.scala +++ b/src/main/scala/firrtl/Driver.scala @@ -4,6 +4,8 @@ package firrtl import java.io.FileNotFoundException +import logger.Logger + import scala.io.Source import Annotations._ @@ -74,6 +76,8 @@ object Driver { def execute(optionsManager: ExecutionOptionsManager with HasFirrtlOptions): FirrtlExecutionResult = { val firrtlConfig = optionsManager.firrtlOptions + Logger.setOptions(optionsManager) + val firrtlSource = firrtlConfig.firrtlSource match { case Some(text) => text.split("\n").toIterator case None => diff --git a/src/main/scala/firrtl/ExecutionOptionsManager.scala b/src/main/scala/firrtl/ExecutionOptionsManager.scala index 05a9f487..ff725e30 100644 --- a/src/main/scala/firrtl/ExecutionOptionsManager.scala +++ b/src/main/scala/firrtl/ExecutionOptionsManager.scala @@ -5,6 +5,7 @@ package firrtl import firrtl.Annotations._ import firrtl.Parser._ import firrtl.passes.memlib.ReplSeqMemAnnotation +import logger.LogLevel import scopt.OptionParser import scala.collection.Seq @@ -15,6 +16,10 @@ import scala.collection.Seq */ trait ComposableOptions +abstract class HasParser(applicationName: String) { + final val parser: OptionParser[Unit] = new OptionParser[Unit](applicationName) {} +} + /** * Most of the chisel toolchain components require a topName which defines a circuit or a device under test. * Much of the work that is done takes place in a directory. @@ -22,10 +27,22 @@ trait ComposableOptions * For example, in chisel, by deferring this it is possible for the execute there to first elaborate the * circuit and then set the topName from that if it has not already been set. */ -case class CommonOptions(topName: String = "", targetDirName: String = "test_run_dir") extends ComposableOptions - -abstract class HasParser(applicationName: String) { - final val parser: OptionParser[Unit] = new OptionParser[Unit](applicationName) {} +case class CommonOptions( + topName: String = "", + targetDirName: String = "test_run_dir", + globalLogLevel: LogLevel.Value = LogLevel.Error, + logToFile: Boolean = false, + logClassNames: Boolean = false, + classLogLevels: Map[String, LogLevel.Value] = Map.empty) extends ComposableOptions { + + def getLogFileName(optionsManager: ExecutionOptionsManager): String = { + if(topName.isEmpty) { + optionsManager.getBuildFileName("log", "firrtl") + } + else { + optionsManager.getBuildFileName("log") + } + } } trait HasCommonOptions { @@ -49,6 +66,61 @@ trait HasCommonOptions { } .text(s"This options defines a work directory for intermediate files, default is ${commonOptions.targetDirName}") + parser.opt[String]("log-level") + .abbr("ll").valueName("<Error|Warn|Info|Debug|Trace>") + .foreach { x => + val level = x.toLowerCase match { + case "error" => LogLevel.Error + case "warn" => LogLevel.Warn + case "info" => LogLevel.Info + case "debug" => LogLevel.Debug + case "trace" => LogLevel.Trace + } + commonOptions = commonOptions.copy(globalLogLevel = level) + } + .validate { x => + if (Array("error", "warn", "info", "debug", "trace").contains(x.toLowerCase)) parser.success + else parser.failure(s"$x bad value must be one of ignore|use|gen|append") + } + .text(s"This options defines a work directory for intermediate files, default is ${commonOptions.targetDirName}") + + parser.opt[Seq[String]]("class-log-level") + .abbr("cll").valueName("<FullClassName:[Error|Warn|Info|Debug|Trace]>[,...]") + .foreach { x => + val logAssignments = x.map { y => + val className :: levelName :: _ = y.split(":").toList + + val level = levelName.toLowerCase match { + case "error" => LogLevel.Error + case "warn" => LogLevel.Warn + case "info" => LogLevel.Info + case "debug" => LogLevel.Debug + case "trace" => LogLevel.Trace + case _ => + throw new Exception(s"Error: bad command line arguments for --class-log-level $x") + } + className -> level + } + + commonOptions = commonOptions.copy(classLogLevels = commonOptions.classLogLevels ++ logAssignments) + + } + .text(s"This options defines a work directory for intermediate files, default is ${commonOptions.targetDirName}") + + parser.opt[Unit]("log-to-file") + .abbr("ltf") + .foreach { _ => + commonOptions = commonOptions.copy(logToFile = true) + } + .text(s"default logs to stdout, this flags writes to topName.log or firrtl.log if no topName") + + parser.opt[Unit]("log-class-names") + .abbr("lcn") + .foreach { _ => + commonOptions = commonOptions.copy(logClassNames = true) + } + .text(s"shows class names and log level in logging output, useful for target --class-log-level") + parser.help("help").text("prints this usage text") } diff --git a/src/main/scala/firrtl/LoweringCompilers.scala b/src/main/scala/firrtl/LoweringCompilers.scala index 53491922..33553179 100644 --- a/src/main/scala/firrtl/LoweringCompilers.scala +++ b/src/main/scala/firrtl/LoweringCompilers.scala @@ -27,11 +27,11 @@ MODIFICATIONS. package firrtl -import com.typesafe.scalalogging.LazyLogging import java.io.Writer import firrtl.passes.Pass import firrtl.ir.Circuit import Annotations._ +import logger.LazyLogging // =========================================== // Utility Traits diff --git a/src/main/scala/logger/Logger.scala b/src/main/scala/logger/Logger.scala new file mode 100644 index 00000000..07855c47 --- /dev/null +++ b/src/main/scala/logger/Logger.scala @@ -0,0 +1,101 @@ +// See LICENSE for license details. + +package logger + +import java.io.{PrintStream, File, FileOutputStream} + +import firrtl.ExecutionOptionsManager + +/** + * This provides a facility for a log4scala* type logging system. Why did we write our own? Because + * the canned ones are just to darned hard to turn on, particularly when embedded in a distribution. + * This one can be turned on programmatically or with the options exposed in the [[firrtl.CommonOptions]] + * and [[ExecutionOptionsManager]] API's in firrtl. + * There are 4 main options. + * * a simple global option to turn on all in scope (and across threads, might want to fix this) + * * turn on specific levels for specific fully qualified class names + * * set a file to write things to, default is just to use stdout + * * include the class names and level in the output. This is useful to figure out what + * the class names that extend LazyLogger are + * + * This is not overly optimized but does pass the string as () => String to avoid string interpolation + * occurring if the the logging level is not sufficiently high. This could be further optimized by playing + * with methods + */ +/** + * The supported log levels, what do they mean? Whatever you want them to. + */ +object LogLevel extends Enumeration { + val Error, Warn, Info, Debug, Trace = Value +} + +/** + * extend this trait to enable logging in a class you are implementing + */ +trait LazyLogging { + val logger = new Logger(this.getClass.getName) +} + +/** + * Singleton in control of what is supposed to get logged, how it's to be logged and where it is to be logged + */ +object Logger { + var globalLevel = LogLevel.Error + val classLevels = new scala.collection.mutable.HashMap[String, LogLevel.Value] + var logClassNames = false + + def showMessage(level: LogLevel.Value, className: String, message: => String): Unit = { + if(globalLevel == level || (classLevels.nonEmpty && classLevels.getOrElse(className, LogLevel.Error) >= level)) { + if(logClassNames) { + stream.println(s"[$level:$className] $message") + } + else { + stream.println(message) + } + } + } + + var stream: PrintStream = System.out + + def setOutput(fileName: String): Unit = { + stream = new PrintStream(new FileOutputStream(new File(fileName))) + } + def setConsole(): Unit = { + stream = Console.out + } + def setClassLogLevels(namesToLevel: Map[String, LogLevel.Value]): Unit = { + classLevels ++= namesToLevel + } + + def setOptions(optionsManager: ExecutionOptionsManager): Unit = { + val commonOptions = optionsManager.commonOptions + globalLevel = commonOptions.globalLogLevel + setClassLogLevels(commonOptions.classLogLevels) + if(commonOptions.logToFile) { + setOutput(commonOptions.getLogFileName(optionsManager)) + } + logClassNames = commonOptions.logClassNames + } +} + +/** + * Classes implementing [[LazyLogging]] will have logger of this type + * @param containerClass passed in from the LazyLogging trait in order to provide class level logging granularity + */ +class Logger(containerClass: String) { + def error(message: => String): Unit = { + Logger.showMessage(LogLevel.Error, containerClass, message) + } + def warn(message: => String): Unit = { + Logger.showMessage(LogLevel.Warn, containerClass, message) + } + def info(message: => String): Unit = { + Logger.showMessage(LogLevel.Info, containerClass, message) + } + def debug(message: => String): Unit = { + Logger.showMessage(LogLevel.Debug, containerClass, message) + } + def trace(message: => String): Unit = { + Logger.showMessage(LogLevel.Trace, containerClass, message) + } +} |
