pkg/logging: added stackTrace support
Plus:
* don't expose mutable children collection from Logger
* remove superfluous field
* other tweaks
BUG=https://code.google.com/p/dart/issues/detail?id=14416
R=sethladd@google.com, sigmund@google.com
Review URL: https://codereview.chromium.org//46233002
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/logging@29324 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/logging.dart b/lib/logging.dart
index 3376145..071b496 100644
--- a/lib/logging.dart
+++ b/lib/logging.dart
@@ -5,9 +5,8 @@
/**
* Support 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.
+ * This library introduces abstractions similar to those used in other
+ * languages, such as the Closure JS Logger and java.util.logging.Logger.
*
* For information on installing and importing this library, see the
* [logging package on pub.dartlang.org]
@@ -16,6 +15,8 @@
library logging;
import 'dart:async';
+import 'package:meta/meta.dart';
+import 'package:unmodifiable_collection/unmodifiable_collection.dart';
/**
* Whether to allow fine-grain logging and configuration of loggers in a
@@ -48,26 +49,26 @@
/** 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. */
- Map<String, Logger> children;
+ final Map<String, Logger> children;
/** Controller used to notify when log entries are added to this logger. */
StreamController<LogRecord> _controller;
- /** The broadcast stream associated with the controller. */
- Stream _stream;
-
/**
* 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 '.'");
}
- 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;
@@ -79,14 +80,13 @@
parent = new Logger(name.substring(0, dot));
thisName = name.substring(dot + 1);
}
- final res = new Logger._internal(thisName, parent);
- _loggers[name] = res;
- return res;
+ return new Logger._internal(thisName, parent, new Map<String, Logger>());
}
- Logger._internal(this.name, this.parent)
- : children = new Map<String, Logger>() {
- if (parent != null) parent.children[name] = this;
+ 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;
}
/**
@@ -102,7 +102,7 @@
}
/** Override the level for this particular [Logger] and its children. */
- set level(Level value) {
+ void set level(Level value) {
if (hierarchicalLoggingEnabled && parent != null) {
_level = value;
} else {
@@ -138,14 +138,18 @@
/**
* 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).
+ * `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, [exception]) {
+ void log(Level logLevel, String message, [Object error,
+ StackTrace stackTrace]) {
if (isLoggable(logLevel)) {
- var record = new LogRecord(logLevel, message, fullName, exception);
+ var record = new LogRecord(logLevel, message, fullName, error,
+ stackTrace);
+
if (hierarchicalLoggingEnabled) {
var target = this;
while (target != null) {
@@ -159,44 +163,43 @@
}
/** Log message at level [Level.FINEST]. */
- void finest(String message, [exception]) =>
- log(Level.FINEST, message, exception);
+ void finest(String message, [Object error, StackTrace stackTrace]) =>
+ log(Level.FINEST, message, error, stackTrace);
/** Log message at level [Level.FINER]. */
- void finer(String message, [exception]) =>
- log(Level.FINER, message, exception);
+ void finer(String message, [Object error, StackTrace stackTrace]) =>
+ log(Level.FINER, message, error, stackTrace);
/** Log message at level [Level.FINE]. */
- void fine(String message, [exception]) =>
- log(Level.FINE, message, exception);
+ void fine(String message, [Object error, StackTrace stackTrace]) =>
+ log(Level.FINE, message, error, stackTrace);
/** Log message at level [Level.CONFIG]. */
- void config(String message, [exception]) =>
- log(Level.CONFIG, message, exception);
+ void config(String message, [Object error, StackTrace stackTrace]) =>
+ log(Level.CONFIG, message, error, stackTrace);
/** Log message at level [Level.INFO]. */
- void info(String message, [exception]) =>
- log(Level.INFO, message, exception);
+ void info(String message, [Object error, StackTrace stackTrace]) =>
+ log(Level.INFO, message, error, stackTrace);
/** Log message at level [Level.WARNING]. */
- void warning(String message, [exception]) =>
- log(Level.WARNING, message, exception);
+ void warning(String message, [Object error, StackTrace stackTrace]) =>
+ log(Level.WARNING, message, error, stackTrace);
/** Log message at level [Level.SEVERE]. */
- void severe(String message, [exception]) =>
- log(Level.SEVERE, message, exception);
+ void severe(String message, [Object error, StackTrace stackTrace]) =>
+ log(Level.SEVERE, message, error, stackTrace);
/** Log message at level [Level.SHOUT]. */
- void shout(String message, [exception]) =>
- log(Level.SHOUT, message, exception);
+ 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);
- _stream = _controller.stream;
}
- return _stream;
+ return _controller.stream;
} else {
return root._getStream();
}
@@ -212,7 +215,7 @@
static Logger get root => new Logger('');
/** All [Logger]s in the system. */
- static Map<String, Logger> _loggers;
+ static final Map<String, Logger> _loggers = <String, Logger>{};
}
@@ -233,7 +236,6 @@
*/
class Level implements Comparable<Level> {
- // TODO(sigmund): mark name/value as 'const' when the language supports it.
final String name;
/**
@@ -274,7 +276,7 @@
/** 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 ==(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;
@@ -304,10 +306,21 @@
static int _nextNumber = 0;
- /** Associated exception (if any) when recording errors messages. */
- var exception;
+ /** Associated error (if any) when recording errors messages. */
+ final Object error;
- LogRecord(this.level, this.message, this.loggerName, [this.exception])
+ // TODO(kevmoo) - remove before V1
+ /**
+ * DEPRECATED. Use [error] instead.
+ */
+ @deprecated
+ Object get exception => 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++;
diff --git a/pubspec.yaml b/pubspec.yaml
index 37ed46e..c23bd3e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,5 +3,8 @@
description: 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.
homepage: http://www.dartlang.org
documentation: http://api.dartlang.org/docs/pkg/logging
+dependencies:
+ meta: any
+ unmodifiable_collection: any
dev_dependencies:
unittest: any
diff --git a/test/logging_test.dart b/test/logging_test.dart
index a631e8c..1c97350 100644
--- a/test/logging_test.dart
+++ b/test/logging_test.dart
@@ -24,8 +24,8 @@
expect(level2 > level1, isTrue);
var level3 = const Level('NOT_REAL3', 253);
- expect(!identical(level1, level3), isTrue); // different instances
- expect(level1 == level3, isTrue); // same value.
+ expect(level1, isNot(same(level3))); // different instances
+ expect(level1, equals(level3)); // same value.
});
test('default levels are in order', () {
@@ -60,8 +60,8 @@
var map = new Map<Level, String>();
map[Level.INFO] = 'info';
map[Level.SHOUT] = 'shout';
- expect(map[Level.INFO], equals('info'));
- expect(map[Level.SHOUT], equals('shout'));
+ expect(map[Level.INFO], same('info'));
+ expect(map[Level.SHOUT], same('shout'));
});
test('logger name cannot start with a "." ', () {
@@ -90,10 +90,10 @@
Logger a = new Logger('a');
Logger b = new Logger('a.b');
Logger c = new Logger('a.c');
- expect(a == b.parent, isTrue);
- expect(a == c.parent, isTrue);
- expect(a.children['b'] == b, isTrue);
- expect(a.children['c'] == c, isTrue);
+ expect(a, same(b.parent));
+ expect(a, same(c.parent));
+ expect(a.children['b'], same(b));
+ expect(a.children['c'], same(c));
});
test('loggers are singletons', () {
@@ -101,10 +101,57 @@
Logger a2 = new Logger('a');
Logger b = new Logger('a.b');
Logger root = Logger.root;
- expect(identical(a1, a2), isTrue);
- expect(identical(a1, b.parent), isTrue);
- expect(identical(root, a1.parent), isTrue);
- expect(identical(root, new Logger('')), isTrue);
+ expect(a1, same(a2));
+ expect(a1, same(b.parent));
+ expect(root, same(a1.parent));
+ expect(root, same(new Logger('')));
+ });
+
+ test('cannot directly manipulate Logger.children', () {
+ var loggerAB = new Logger('a.b');
+ var loggerA = loggerAB.parent;
+
+ expect(loggerA.children['b'], same(loggerAB), reason: 'can read Children');
+
+ expect(() {
+ loggerAB.children['test'] = null;
+ }, throwsUnsupportedError, reason: 'Children is read-only');
+ });
+
+ test('stackTrace gets throw to LogRecord', () {
+ Logger.root.level = Level.INFO;
+
+ var records = new List<LogRecord>();
+
+ var sub = Logger.root.onRecord.listen(records.add);
+
+ try {
+ throw new UnsupportedError('test exception');
+ } catch(error, stack) {
+ Logger.root.log(Level.SEVERE, 'severe', error, stack);
+ Logger.root.warning('warning', error, stack);
+ }
+
+ Logger.root.log(Level.SHOUT, 'shout');
+
+ sub.cancel();
+
+ expect(records, hasLength(3));
+
+ var severe = records[0];
+ expect(severe.message, 'severe');
+ expect(severe.error is UnsupportedError, isTrue);
+ expect(severe.stackTrace is StackTrace, isTrue);
+
+ var warning = records[1];
+ expect(warning.message, 'warning');
+ expect(warning.error is UnsupportedError, isTrue);
+ expect(warning.stackTrace is StackTrace, isTrue);
+
+ var shout = records[2];
+ expect(shout.message, 'shout');
+ expect(shout.error, isNull);
+ expect(shout.stackTrace, isNull);
});
group('mutating levels', () {
@@ -134,7 +181,7 @@
});
test('cannot set level if hierarchy is disabled', () {
- expect(() {a.level = Level.FINE;}, throws);
+ expect(() {a.level = Level.FINE;}, throwsUnsupportedError);
});
test('loggers effective level - no hierarchy', () {
@@ -176,7 +223,7 @@
expect(root.isLoggable(Level.WARNING), isFalse);
expect(c.isLoggable(Level.FINEST), isTrue);
expect(c.isLoggable(Level.FINE), isTrue);
- expect(!e.isLoggable(Level.SHOUT), isTrue);
+ expect(e.isLoggable(Level.SHOUT), isFalse);
});
test('add/remove handlers - no hierarchy', () {