| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /** |
| * Provides APIs for debugging and error logging. This library introduces |
| * abstractions similar to those used in other languages, such as the Closure JS |
| * Logger and java.util.logging.Logger. |
| */ |
| library logging; |
| |
| /** |
| * Whether to allow fine-grain logging and configuration of loggers in a |
| * hierarchy. When false, all logging is merged in the root logger. |
| */ |
| bool hierarchicalLoggingEnabled = false; |
| |
| /** |
| * Level for the root-logger. This will be the level of all loggers if |
| * [hierarchicalLoggingEnabled] is false. |
| */ |
| Level _rootLevel = Level.INFO; |
| |
| |
| /** |
| * Use a [Logger] to log debug messages. [Logger]s are named using a |
| * hierarchical dot-separated name convention. |
| */ |
| class Logger { |
| /** Simple name of this logger. */ |
| final String name; |
| |
| /** The full name of this logger, which includes the parent's full name. */ |
| String get fullName => |
| (parent == null || parent.name == '') ? name : '${parent.fullName}.$name'; |
| |
| /** Parent of this logger in the hierarchy of loggers. */ |
| final Logger parent; |
| |
| /** Logging [Level] used for entries generated on this logger. */ |
| Level _level; |
| |
| /** Children in the hierarchy of loggers, indexed by their simple names. */ |
| Map<String, Logger> children; |
| |
| /** Handlers used to process log entries in this logger. */ |
| List<LoggerHandler> _handlers; |
| |
| /** |
| * Singleton constructor. Calling `new Logger(name)` will return the same |
| * actual instance whenever it is called with the same string name. |
| */ |
| factory Logger(String name) { |
| if (name.startsWith('.')) { |
| throw new ArgumentError("name shouldn't start with a '.'"); |
| } |
| if (_loggers == null) _loggers = <String, Logger>{}; |
| if (_loggers.containsKey(name)) return _loggers[name]; |
| |
| // Split hierarchical names (separated with '.'). |
| int dot = name.lastIndexOf('.'); |
| Logger parent = null; |
| String thisName; |
| if (dot == -1) { |
| if (name != '') parent = new Logger(''); |
| thisName = name; |
| } else { |
| parent = new Logger(name.substring(0, dot)); |
| thisName = name.substring(dot + 1); |
| } |
| final res = new Logger._internal(thisName, parent); |
| _loggers[name] = res; |
| return res; |
| } |
| |
| Logger._internal(this.name, this.parent) |
| : children = new Map<String, Logger>() { |
| if (parent != null) parent.children[name] = this; |
| } |
| |
| /** |
| * Effective level considering the levels established in this logger's parents |
| * (when [hierarchicalLoggingEnabled] is true). |
| */ |
| Level get level { |
| if (hierarchicalLoggingEnabled) { |
| if (_level != null) return _level; |
| if (parent != null) return parent.level; |
| } |
| return _rootLevel; |
| } |
| |
| /** Override the level for this particular [Logger] and its children. */ |
| set level(value) { |
| if (hierarchicalLoggingEnabled && parent != null) { |
| _level = value; |
| } else { |
| if (parent != null) { |
| throw new UnsupportedError( |
| 'Please set "hierarchicalLoggingEnabled" to true if you want to ' |
| 'change the level on a non-root logger.'); |
| } |
| _rootLevel = value; |
| } |
| } |
| |
| /** |
| * Returns an event manager for this [Logger]. You can listen for log messages |
| * by adding a [LoggerHandler] to an event from the event manager, for |
| * instance: |
| * logger.on.record.add((record) { ... }); |
| */ |
| LoggerEvents get on => new LoggerEvents(this); |
| |
| /** Adds a handler to listen whenever a log record is added to this logger. */ |
| void _addHandler(LoggerHandler handler) { |
| if (hierarchicalLoggingEnabled || parent == null) { |
| if (_handlers == null) { |
| _handlers = new List<LoggerHandler>(); |
| } |
| _handlers.add(handler); |
| } else { |
| root._addHandler(handler); |
| } |
| } |
| |
| /** Remove a previously added handler. */ |
| void _removeHandler(LoggerHandler handler) { |
| if (hierarchicalLoggingEnabled || parent == null) { |
| if (_handlers == null) return; |
| int index = _handlers.indexOf(handler); |
| if (index != -1) _handlers.removeRange(index, 1); |
| } else { |
| root._removeHandler(handler); |
| } |
| } |
| |
| /** Removes all handlers previously added to this logger. */ |
| void _clearHandlers() { |
| if (hierarchicalLoggingEnabled || parent == null) { |
| _handlers = null; |
| } else { |
| root._clearHandlers(); |
| } |
| } |
| |
| /** Whether a message for [value]'s level is loggable in this logger. */ |
| bool isLoggable(Level value) => (value >= level); |
| |
| /** |
| * Adds a log record for a [message] at a particular [logLevel] if |
| * `isLoggable(logLevel)` is true. Use this method to create log entries for |
| * user-defined levels. To record a message at a predefined level (e.g. |
| * [Level.INFO], [Level.WARNING], etc) you can use their specialized methods |
| * instead (e.g. [info], [warning], etc). |
| */ |
| // TODO(sigmund): add support for logging exceptions. |
| void log(Level logLevel, String message) { |
| if (isLoggable(logLevel)) { |
| var record = new LogRecord(logLevel, message, fullName); |
| if (hierarchicalLoggingEnabled) { |
| var target = this; |
| while (target != null) { |
| target._publish(record); |
| target = target.parent; |
| } |
| } else { |
| root._publish(record); |
| } |
| } |
| } |
| |
| /** Log message at level [Level.FINEST]. */ |
| void finest(String message) => log(Level.FINEST, message); |
| |
| /** Log message at level [Level.FINER]. */ |
| void finer(String message) => log(Level.FINER, message); |
| |
| /** Log message at level [Level.FINE]. */ |
| void fine(String message) => log(Level.FINE, message); |
| |
| /** Log message at level [Level.CONFIG]. */ |
| void config(String message) => log(Level.CONFIG, message); |
| |
| /** Log message at level [Level.INFO]. */ |
| void info(String message) => log(Level.INFO, message); |
| |
| /** Log message at level [Level.WARNING]. */ |
| void warning(String message) => log(Level.WARNING, message); |
| |
| /** Log message at level [Level.SEVERE]. */ |
| void severe(String message) => log(Level.SEVERE, message); |
| |
| /** Log message at level [Level.SHOUT]. */ |
| void shout(String message) => log(Level.SHOUT, message); |
| |
| void _publish(LogRecord record) { |
| if (_handlers != null) { |
| _handlers.forEach((h) => h(record)); |
| } |
| } |
| |
| /** Top-level root [Logger]. */ |
| static get root => new Logger(''); |
| |
| /** All [Logger]s in the system. */ |
| static Map<String, Logger> _loggers; |
| } |
| |
| |
| /** Handler callback to process log entries as they are added to a [Logger]. */ |
| typedef void LoggerHandler(LogRecord); |
| |
| |
| /** Event manager for a [Logger] (holds events that a [Logger] can fire). */ |
| class LoggerEvents { |
| final Logger _logger; |
| |
| LoggerEvents(this._logger); |
| |
| /** Event fired when a log record is added to a [Logger]. */ |
| LoggerHandlerList get record => new LoggerHandlerList(_logger); |
| } |
| |
| |
| /** List of handlers that will be called on a logger event. */ |
| class LoggerHandlerList { |
| Logger _logger; |
| |
| LoggerHandlerList(this._logger); |
| |
| void add(LoggerHandler handler) => _logger._addHandler(handler); |
| void remove(LoggerHandler handler) => _logger._removeHandler(handler); |
| void clear() => _logger._clearHandlers(); |
| } |
| |
| |
| /** |
| * [Level]s to control logging output. Logging can be enabled to include all |
| * levels above certain [Level]. [Level]s are ordered using an integer |
| * value [Level.value]. The predefined [Level] constants below are sorted as |
| * follows (in descending order): [Level.SHOUT], [Level.SEVERE], |
| * [Level.WARNING], [Level.INFO], [Level.CONFIG], [Level.FINE], [Level.FINER], |
| * [Level.FINEST], and [Level.ALL]. |
| * |
| * We recommend using one of the predefined logging levels. If you define your |
| * own level, make sure you use a value between those used in [Level.ALL] and |
| * [Level.OFF]. |
| */ |
| class Level implements Comparable<Level> { |
| |
| // TODO(sigmund): mark name/value as 'const' when the language supports it. |
| final String name; |
| |
| /** |
| * Unique value for this level. Used to order levels, so filtering can exclude |
| * messages whose level is under certain value. |
| */ |
| final int value; |
| |
| const Level(this.name, this.value); |
| |
| /** Special key to turn on logging for all levels ([value] = 0). */ |
| static const Level ALL = const Level('ALL', 0); |
| |
| /** Special key to turn off all logging ([value] = 2000). */ |
| static const Level OFF = const Level('OFF', 2000); |
| |
| /** Key for highly detailed tracing ([value] = 300). */ |
| static const Level FINEST = const Level('FINEST', 300); |
| |
| /** Key for fairly detailed tracing ([value] = 400). */ |
| static const Level FINER = const Level('FINER', 400); |
| |
| /** Key for tracing information ([value] = 500). */ |
| static const Level FINE = const Level('FINE', 500); |
| |
| /** Key for static configuration messages ([value] = 700). */ |
| static const Level CONFIG = const Level('CONFIG', 700); |
| |
| /** Key for informational messages ([value] = 800). */ |
| static const Level INFO = const Level('INFO', 800); |
| |
| /** Key for potential problems ([value] = 900). */ |
| static const Level WARNING = const Level('WARNING', 900); |
| |
| /** Key for serious failures ([value] = 1000). */ |
| static const Level SEVERE = const Level('SEVERE', 1000); |
| |
| /** Key for extra debugging loudness ([value] = 1200). */ |
| static const Level SHOUT = const Level('SHOUT', 1200); |
| |
| bool operator ==(Level other) => other != null && value == other.value; |
| bool operator <(Level other) => value < other.value; |
| bool operator <=(Level other) => value <= other.value; |
| bool operator >(Level other) => value > other.value; |
| bool operator >=(Level other) => value >= other.value; |
| int compareTo(Level other) => value - other.value; |
| int get hashCode => value; |
| String toString() => name; |
| } |
| |
| |
| /** |
| * A log entry representation used to propagate information from [Logger] to |
| * individual [Handler]s. |
| */ |
| class LogRecord { |
| final Level level; |
| final String message; |
| |
| /** Logger where this record is stored. */ |
| final String loggerName; |
| |
| /** Time when this record was created. */ |
| final DateTime time; |
| |
| /** Unique sequence number greater than all log records created before it. */ |
| final int sequenceNumber; |
| |
| static int _nextNumber = 0; |
| |
| /** Associated exception (if any) when recording errors messages. */ |
| Exception exception; |
| |
| /** Associated exception message (if any) when recording errors messages. */ |
| String exceptionText; |
| |
| LogRecord( |
| this.level, this.message, this.loggerName, |
| [time, this.exception, this.exceptionText]) : |
| this.time = (time == null) ? new DateTime.now() : time, |
| this.sequenceNumber = LogRecord._nextNumber++; |
| } |