047b32681d
R=sgjesse@google.com Review URL: https://codereview.chromium.org//113883002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@31260 260f80e4-7a28-3924-810f-c04153c831b5
360 lines
12 KiB
Dart
360 lines
12 KiB
Dart
// 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.
|
|
|
|
/**
|
|
* Support for logging.
|
|
*
|
|
* For information on installing and importing this library, see the
|
|
* [logging package on pub.dartlang.org]
|
|
* (http://pub.dartlang.org/packages/logging).
|
|
*
|
|
* ## Initializing
|
|
*
|
|
* By default, the logging package does not do anything useful with the
|
|
* log messages. You must configure the logging level and add a handler
|
|
* for the log messages.
|
|
*
|
|
* Here is a simple logging configuration that logs all messages
|
|
* via `print`.
|
|
*
|
|
* Logger.root.level = Level.ALL;
|
|
* Logger.root.onRecord.listen((LogRecord rec) {
|
|
* print('${rec.level.name}: ${rec.time}: ${rec.message}');
|
|
* });
|
|
*
|
|
* First, set the root [Level]. All messages at or above the level are
|
|
* sent to the [onRecord] stream.
|
|
*
|
|
* Then, listen on the [onRecord] stream for [LogRecord] events. The
|
|
* [LogRecord] class has various properties for the message, error,
|
|
* logger name, and more.
|
|
*
|
|
* ## Logging messages
|
|
*
|
|
* Create a [Logger] with a unique name to easily identify the source
|
|
* of the log messages.
|
|
*
|
|
* final Logger log = new Logger('MyClassName');
|
|
*
|
|
* Here is an example of logging a debug message and an error:
|
|
*
|
|
* Future future = doSomethingAsync();
|
|
* future.then((result) {
|
|
* log.fine('Got the result: $result');
|
|
* processResult(result);
|
|
* })
|
|
* .catchError((e, stackTrace) => log.severe('Oh noes!', e, stackTrace));
|
|
*
|
|
* See the [Logger] class for the different logging methods.
|
|
*/
|
|
library logging;
|
|
|
|
import 'dart:async';
|
|
import 'package:collection/wrappers.dart';
|
|
|
|
/**
|
|
* 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;
|
|
|
|
final Map<String, Logger> _children;
|
|
|
|
/** Children in the hierarchy of loggers, indexed by their simple names. */
|
|
final Map<String, Logger> children;
|
|
|
|
/** Controller used to notify when log entries are added to this logger. */
|
|
StreamController<LogRecord> _controller;
|
|
|
|
/**
|
|
* 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) {
|
|
return _loggers.putIfAbsent(name, () => new Logger._named(name));
|
|
}
|
|
|
|
factory Logger._named(String name) {
|
|
if (name.startsWith('.')) {
|
|
throw new ArgumentError("name shouldn't start with a '.'");
|
|
}
|
|
// 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);
|
|
}
|
|
return new Logger._internal(thisName, parent, new Map<String, Logger>());
|
|
}
|
|
|
|
Logger._internal(this.name, this.parent, Map<String, Logger> children) :
|
|
this._children = children,
|
|
this.children = new UnmodifiableMapView(children) {
|
|
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. */
|
|
void set level(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 stream of messages added to this [Logger]. You can listen for
|
|
* messages using the standard stream APIs, for instance:
|
|
* logger.onRecord.listen((record) { ... });
|
|
*/
|
|
Stream<LogRecord> get onRecord => _getStream();
|
|
|
|
void clearListeners() {
|
|
if (hierarchicalLoggingEnabled || parent == null) {
|
|
if (_controller != null) {
|
|
_controller.close();
|
|
_controller = null;
|
|
}
|
|
} else {
|
|
root.clearListeners();
|
|
}
|
|
}
|
|
|
|
/** 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).
|
|
*/
|
|
void log(Level logLevel, String message, [Object error,
|
|
StackTrace stackTrace]) {
|
|
if (isLoggable(logLevel)) {
|
|
var record = new LogRecord(logLevel, message, fullName, error,
|
|
stackTrace);
|
|
|
|
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, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.FINEST, message, error, stackTrace);
|
|
|
|
/** Log message at level [Level.FINER]. */
|
|
void finer(String message, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.FINER, message, error, stackTrace);
|
|
|
|
/** Log message at level [Level.FINE]. */
|
|
void fine(String message, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.FINE, message, error, stackTrace);
|
|
|
|
/** Log message at level [Level.CONFIG]. */
|
|
void config(String message, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.CONFIG, message, error, stackTrace);
|
|
|
|
/** Log message at level [Level.INFO]. */
|
|
void info(String message, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.INFO, message, error, stackTrace);
|
|
|
|
/** Log message at level [Level.WARNING]. */
|
|
void warning(String message, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.WARNING, message, error, stackTrace);
|
|
|
|
/** Log message at level [Level.SEVERE]. */
|
|
void severe(String message, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.SEVERE, message, error, stackTrace);
|
|
|
|
/** Log message at level [Level.SHOUT]. */
|
|
void shout(String message, [Object error, StackTrace stackTrace]) =>
|
|
log(Level.SHOUT, message, error, stackTrace);
|
|
|
|
Stream<LogRecord> _getStream() {
|
|
if (hierarchicalLoggingEnabled || parent == null) {
|
|
if (_controller == null) {
|
|
_controller = new StreamController<LogRecord>.broadcast(sync: true);
|
|
}
|
|
return _controller.stream;
|
|
} else {
|
|
return root._getStream();
|
|
}
|
|
}
|
|
|
|
void _publish(LogRecord record) {
|
|
if (_controller != null) {
|
|
_controller.add(record);
|
|
}
|
|
}
|
|
|
|
/** Top-level root [Logger]. */
|
|
static Logger get root => new Logger('');
|
|
|
|
/** All [Logger]s in the system. */
|
|
static final Map<String, Logger> _loggers = <String, Logger>{};
|
|
}
|
|
|
|
|
|
/** Handler callback to process log entries as they are added to a [Logger]. */
|
|
typedef void LoggerHandler(LogRecord);
|
|
|
|
/**
|
|
* [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> {
|
|
|
|
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);
|
|
|
|
static const List<Level> LEVELS = const
|
|
[ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, SHOUT, OFF];
|
|
|
|
bool operator ==(Object other) => other is Level && 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 error (if any) when recording errors messages. */
|
|
final Object error;
|
|
|
|
/** Associated stackTrace (if any) when recording errors messages. */
|
|
final StackTrace stackTrace;
|
|
|
|
LogRecord(this.level, this.message, this.loggerName, [this.error,
|
|
this.stackTrace])
|
|
: time = new DateTime.now(),
|
|
sequenceNumber = LogRecord._nextNumber++;
|
|
|
|
String toString() => '[${level.name}] $loggerName: $message';
|
|
}
|