| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Support for debugging and error logging. | 6 * Support for debugging and error logging. |
| 7 * | 7 * |
| 8 * This library introduces abstractions similar to | 8 * This library introduces abstractions similar to those used in other |
| 9 * those used in other languages, such as the Closure JS | 9 * languages, such as the Closure JS Logger and java.util.logging.Logger. |
| 10 * Logger and java.util.logging.Logger. | |
| 11 * | 10 * |
| 12 * For information on installing and importing this library, see the | 11 * For information on installing and importing this library, see the |
| 13 * [logging package on pub.dartlang.org] | 12 * [logging package on pub.dartlang.org] |
| 14 * (http://pub.dartlang.org/packages/logging). | 13 * (http://pub.dartlang.org/packages/logging). |
| 15 */ | 14 */ |
| 16 library logging; | 15 library logging; |
| 17 | 16 |
| 18 import 'dart:async'; | 17 import 'dart:async'; |
| 18 import 'package:meta/meta.dart'; |
| 19 import 'package:unmodifiable_collection/unmodifiable_collection.dart'; |
| 19 | 20 |
| 20 /** | 21 /** |
| 21 * Whether to allow fine-grain logging and configuration of loggers in a | 22 * Whether to allow fine-grain logging and configuration of loggers in a |
| 22 * hierarchy. When false, all logging is merged in the root logger. | 23 * hierarchy. When false, all logging is merged in the root logger. |
| 23 */ | 24 */ |
| 24 bool hierarchicalLoggingEnabled = false; | 25 bool hierarchicalLoggingEnabled = false; |
| 25 | 26 |
| 26 /** | 27 /** |
| 27 * Level for the root-logger. This will be the level of all loggers if | 28 * Level for the root-logger. This will be the level of all loggers if |
| 28 * [hierarchicalLoggingEnabled] is false. | 29 * [hierarchicalLoggingEnabled] is false. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 41 /** The full name of this logger, which includes the parent's full name. */ | 42 /** The full name of this logger, which includes the parent's full name. */ |
| 42 String get fullName => | 43 String get fullName => |
| 43 (parent == null || parent.name == '') ? name : '${parent.fullName}.$name'; | 44 (parent == null || parent.name == '') ? name : '${parent.fullName}.$name'; |
| 44 | 45 |
| 45 /** Parent of this logger in the hierarchy of loggers. */ | 46 /** Parent of this logger in the hierarchy of loggers. */ |
| 46 final Logger parent; | 47 final Logger parent; |
| 47 | 48 |
| 48 /** Logging [Level] used for entries generated on this logger. */ | 49 /** Logging [Level] used for entries generated on this logger. */ |
| 49 Level _level; | 50 Level _level; |
| 50 | 51 |
| 52 final Map<String, Logger> _children; |
| 53 |
| 51 /** Children in the hierarchy of loggers, indexed by their simple names. */ | 54 /** Children in the hierarchy of loggers, indexed by their simple names. */ |
| 52 Map<String, Logger> children; | 55 final Map<String, Logger> children; |
| 53 | 56 |
| 54 /** Controller used to notify when log entries are added to this logger. */ | 57 /** Controller used to notify when log entries are added to this logger. */ |
| 55 StreamController<LogRecord> _controller; | 58 StreamController<LogRecord> _controller; |
| 56 | 59 |
| 57 /** The broadcast stream associated with the controller. */ | |
| 58 Stream _stream; | |
| 59 | |
| 60 /** | 60 /** |
| 61 * Singleton constructor. Calling `new Logger(name)` will return the same | 61 * Singleton constructor. Calling `new Logger(name)` will return the same |
| 62 * actual instance whenever it is called with the same string name. | 62 * actual instance whenever it is called with the same string name. |
| 63 */ | 63 */ |
| 64 factory Logger(String name) { | 64 factory Logger(String name) { |
| 65 return _loggers.putIfAbsent(name, () => new Logger._named(name)); |
| 66 } |
| 67 |
| 68 factory Logger._named(String name) { |
| 65 if (name.startsWith('.')) { | 69 if (name.startsWith('.')) { |
| 66 throw new ArgumentError("name shouldn't start with a '.'"); | 70 throw new ArgumentError("name shouldn't start with a '.'"); |
| 67 } | 71 } |
| 68 if (_loggers == null) _loggers = <String, Logger>{}; | |
| 69 if (_loggers.containsKey(name)) return _loggers[name]; | |
| 70 | |
| 71 // Split hierarchical names (separated with '.'). | 72 // Split hierarchical names (separated with '.'). |
| 72 int dot = name.lastIndexOf('.'); | 73 int dot = name.lastIndexOf('.'); |
| 73 Logger parent = null; | 74 Logger parent = null; |
| 74 String thisName; | 75 String thisName; |
| 75 if (dot == -1) { | 76 if (dot == -1) { |
| 76 if (name != '') parent = new Logger(''); | 77 if (name != '') parent = new Logger(''); |
| 77 thisName = name; | 78 thisName = name; |
| 78 } else { | 79 } else { |
| 79 parent = new Logger(name.substring(0, dot)); | 80 parent = new Logger(name.substring(0, dot)); |
| 80 thisName = name.substring(dot + 1); | 81 thisName = name.substring(dot + 1); |
| 81 } | 82 } |
| 82 final res = new Logger._internal(thisName, parent); | 83 return new Logger._internal(thisName, parent, new Map<String, Logger>()); |
| 83 _loggers[name] = res; | |
| 84 return res; | |
| 85 } | 84 } |
| 86 | 85 |
| 87 Logger._internal(this.name, this.parent) | 86 Logger._internal(this.name, this.parent, Map<String, Logger> children) : |
| 88 : children = new Map<String, Logger>() { | 87 this._children = children, |
| 89 if (parent != null) parent.children[name] = this; | 88 this.children = new UnmodifiableMapView(children) { |
| 89 if (parent != null) parent._children[name] = this; |
| 90 } | 90 } |
| 91 | 91 |
| 92 /** | 92 /** |
| 93 * Effective level considering the levels established in this logger's parents | 93 * Effective level considering the levels established in this logger's parents |
| 94 * (when [hierarchicalLoggingEnabled] is true). | 94 * (when [hierarchicalLoggingEnabled] is true). |
| 95 */ | 95 */ |
| 96 Level get level { | 96 Level get level { |
| 97 if (hierarchicalLoggingEnabled) { | 97 if (hierarchicalLoggingEnabled) { |
| 98 if (_level != null) return _level; | 98 if (_level != null) return _level; |
| 99 if (parent != null) return parent.level; | 99 if (parent != null) return parent.level; |
| 100 } | 100 } |
| 101 return _rootLevel; | 101 return _rootLevel; |
| 102 } | 102 } |
| 103 | 103 |
| 104 /** Override the level for this particular [Logger] and its children. */ | 104 /** Override the level for this particular [Logger] and its children. */ |
| 105 set level(Level value) { | 105 void set level(Level value) { |
| 106 if (hierarchicalLoggingEnabled && parent != null) { | 106 if (hierarchicalLoggingEnabled && parent != null) { |
| 107 _level = value; | 107 _level = value; |
| 108 } else { | 108 } else { |
| 109 if (parent != null) { | 109 if (parent != null) { |
| 110 throw new UnsupportedError( | 110 throw new UnsupportedError( |
| 111 'Please set "hierarchicalLoggingEnabled" to true if you want to ' | 111 'Please set "hierarchicalLoggingEnabled" to true if you want to ' |
| 112 'change the level on a non-root logger.'); | 112 'change the level on a non-root logger.'); |
| 113 } | 113 } |
| 114 _rootLevel = value; | 114 _rootLevel = value; |
| 115 } | 115 } |
| (...skipping 15 matching lines...) Expand all Loading... |
| 131 } else { | 131 } else { |
| 132 root.clearListeners(); | 132 root.clearListeners(); |
| 133 } | 133 } |
| 134 } | 134 } |
| 135 | 135 |
| 136 /** Whether a message for [value]'s level is loggable in this logger. */ | 136 /** Whether a message for [value]'s level is loggable in this logger. */ |
| 137 bool isLoggable(Level value) => (value >= level); | 137 bool isLoggable(Level value) => (value >= level); |
| 138 | 138 |
| 139 /** | 139 /** |
| 140 * Adds a log record for a [message] at a particular [logLevel] if | 140 * Adds a log record for a [message] at a particular [logLevel] if |
| 141 * `isLoggable(logLevel)` is true. Use this method to create log entries for | 141 * `isLoggable(logLevel)` is true. |
| 142 * user-defined levels. To record a message at a predefined level (e.g. | 142 * |
| 143 * [Level.INFO], [Level.WARNING], etc) you can use their specialized methods | 143 * Use this method to create log entries for user-defined levels. To record a |
| 144 * instead (e.g. [info], [warning], etc). | 144 * message at a predefined level (e.g. [Level.INFO], [Level.WARNING], etc) you |
| 145 * can use their specialized methods instead (e.g. [info], [warning], etc). |
| 145 */ | 146 */ |
| 146 void log(Level logLevel, String message, [exception]) { | 147 void log(Level logLevel, String message, [Object error, |
| 148 StackTrace stackTrace]) { |
| 147 if (isLoggable(logLevel)) { | 149 if (isLoggable(logLevel)) { |
| 148 var record = new LogRecord(logLevel, message, fullName, exception); | 150 var record = new LogRecord(logLevel, message, fullName, error, |
| 151 stackTrace); |
| 152 |
| 149 if (hierarchicalLoggingEnabled) { | 153 if (hierarchicalLoggingEnabled) { |
| 150 var target = this; | 154 var target = this; |
| 151 while (target != null) { | 155 while (target != null) { |
| 152 target._publish(record); | 156 target._publish(record); |
| 153 target = target.parent; | 157 target = target.parent; |
| 154 } | 158 } |
| 155 } else { | 159 } else { |
| 156 root._publish(record); | 160 root._publish(record); |
| 157 } | 161 } |
| 158 } | 162 } |
| 159 } | 163 } |
| 160 | 164 |
| 161 /** Log message at level [Level.FINEST]. */ | 165 /** Log message at level [Level.FINEST]. */ |
| 162 void finest(String message, [exception]) => | 166 void finest(String message, [Object error, StackTrace stackTrace]) => |
| 163 log(Level.FINEST, message, exception); | 167 log(Level.FINEST, message, error, stackTrace); |
| 164 | 168 |
| 165 /** Log message at level [Level.FINER]. */ | 169 /** Log message at level [Level.FINER]. */ |
| 166 void finer(String message, [exception]) => | 170 void finer(String message, [Object error, StackTrace stackTrace]) => |
| 167 log(Level.FINER, message, exception); | 171 log(Level.FINER, message, error, stackTrace); |
| 168 | 172 |
| 169 /** Log message at level [Level.FINE]. */ | 173 /** Log message at level [Level.FINE]. */ |
| 170 void fine(String message, [exception]) => | 174 void fine(String message, [Object error, StackTrace stackTrace]) => |
| 171 log(Level.FINE, message, exception); | 175 log(Level.FINE, message, error, stackTrace); |
| 172 | 176 |
| 173 /** Log message at level [Level.CONFIG]. */ | 177 /** Log message at level [Level.CONFIG]. */ |
| 174 void config(String message, [exception]) => | 178 void config(String message, [Object error, StackTrace stackTrace]) => |
| 175 log(Level.CONFIG, message, exception); | 179 log(Level.CONFIG, message, error, stackTrace); |
| 176 | 180 |
| 177 /** Log message at level [Level.INFO]. */ | 181 /** Log message at level [Level.INFO]. */ |
| 178 void info(String message, [exception]) => | 182 void info(String message, [Object error, StackTrace stackTrace]) => |
| 179 log(Level.INFO, message, exception); | 183 log(Level.INFO, message, error, stackTrace); |
| 180 | 184 |
| 181 /** Log message at level [Level.WARNING]. */ | 185 /** Log message at level [Level.WARNING]. */ |
| 182 void warning(String message, [exception]) => | 186 void warning(String message, [Object error, StackTrace stackTrace]) => |
| 183 log(Level.WARNING, message, exception); | 187 log(Level.WARNING, message, error, stackTrace); |
| 184 | 188 |
| 185 /** Log message at level [Level.SEVERE]. */ | 189 /** Log message at level [Level.SEVERE]. */ |
| 186 void severe(String message, [exception]) => | 190 void severe(String message, [Object error, StackTrace stackTrace]) => |
| 187 log(Level.SEVERE, message, exception); | 191 log(Level.SEVERE, message, error, stackTrace); |
| 188 | 192 |
| 189 /** Log message at level [Level.SHOUT]. */ | 193 /** Log message at level [Level.SHOUT]. */ |
| 190 void shout(String message, [exception]) => | 194 void shout(String message, [Object error, StackTrace stackTrace]) => |
| 191 log(Level.SHOUT, message, exception); | 195 log(Level.SHOUT, message, error, stackTrace); |
| 192 | 196 |
| 193 Stream<LogRecord> _getStream() { | 197 Stream<LogRecord> _getStream() { |
| 194 if (hierarchicalLoggingEnabled || parent == null) { | 198 if (hierarchicalLoggingEnabled || parent == null) { |
| 195 if (_controller == null) { | 199 if (_controller == null) { |
| 196 _controller = new StreamController<LogRecord>.broadcast(sync: true); | 200 _controller = new StreamController<LogRecord>.broadcast(sync: true); |
| 197 _stream = _controller.stream; | |
| 198 } | 201 } |
| 199 return _stream; | 202 return _controller.stream; |
| 200 } else { | 203 } else { |
| 201 return root._getStream(); | 204 return root._getStream(); |
| 202 } | 205 } |
| 203 } | 206 } |
| 204 | 207 |
| 205 void _publish(LogRecord record) { | 208 void _publish(LogRecord record) { |
| 206 if (_controller != null) { | 209 if (_controller != null) { |
| 207 _controller.add(record); | 210 _controller.add(record); |
| 208 } | 211 } |
| 209 } | 212 } |
| 210 | 213 |
| 211 /** Top-level root [Logger]. */ | 214 /** Top-level root [Logger]. */ |
| 212 static Logger get root => new Logger(''); | 215 static Logger get root => new Logger(''); |
| 213 | 216 |
| 214 /** All [Logger]s in the system. */ | 217 /** All [Logger]s in the system. */ |
| 215 static Map<String, Logger> _loggers; | 218 static final Map<String, Logger> _loggers = <String, Logger>{}; |
| 216 } | 219 } |
| 217 | 220 |
| 218 | 221 |
| 219 /** Handler callback to process log entries as they are added to a [Logger]. */ | 222 /** Handler callback to process log entries as they are added to a [Logger]. */ |
| 220 typedef void LoggerHandler(LogRecord); | 223 typedef void LoggerHandler(LogRecord); |
| 221 | 224 |
| 222 /** | 225 /** |
| 223 * [Level]s to control logging output. Logging can be enabled to include all | 226 * [Level]s to control logging output. Logging can be enabled to include all |
| 224 * levels above certain [Level]. [Level]s are ordered using an integer | 227 * levels above certain [Level]. [Level]s are ordered using an integer |
| 225 * value [Level.value]. The predefined [Level] constants below are sorted as | 228 * value [Level.value]. The predefined [Level] constants below are sorted as |
| 226 * follows (in descending order): [Level.SHOUT], [Level.SEVERE], | 229 * follows (in descending order): [Level.SHOUT], [Level.SEVERE], |
| 227 * [Level.WARNING], [Level.INFO], [Level.CONFIG], [Level.FINE], [Level.FINER], | 230 * [Level.WARNING], [Level.INFO], [Level.CONFIG], [Level.FINE], [Level.FINER], |
| 228 * [Level.FINEST], and [Level.ALL]. | 231 * [Level.FINEST], and [Level.ALL]. |
| 229 * | 232 * |
| 230 * We recommend using one of the predefined logging levels. If you define your | 233 * We recommend using one of the predefined logging levels. If you define your |
| 231 * own level, make sure you use a value between those used in [Level.ALL] and | 234 * own level, make sure you use a value between those used in [Level.ALL] and |
| 232 * [Level.OFF]. | 235 * [Level.OFF]. |
| 233 */ | 236 */ |
| 234 class Level implements Comparable<Level> { | 237 class Level implements Comparable<Level> { |
| 235 | 238 |
| 236 // TODO(sigmund): mark name/value as 'const' when the language supports it. | |
| 237 final String name; | 239 final String name; |
| 238 | 240 |
| 239 /** | 241 /** |
| 240 * Unique value for this level. Used to order levels, so filtering can exclude | 242 * Unique value for this level. Used to order levels, so filtering can exclude |
| 241 * messages whose level is under certain value. | 243 * messages whose level is under certain value. |
| 242 */ | 244 */ |
| 243 final int value; | 245 final int value; |
| 244 | 246 |
| 245 const Level(this.name, this.value); | 247 const Level(this.name, this.value); |
| 246 | 248 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 267 | 269 |
| 268 /** Key for potential problems ([value] = 900). */ | 270 /** Key for potential problems ([value] = 900). */ |
| 269 static const Level WARNING = const Level('WARNING', 900); | 271 static const Level WARNING = const Level('WARNING', 900); |
| 270 | 272 |
| 271 /** Key for serious failures ([value] = 1000). */ | 273 /** Key for serious failures ([value] = 1000). */ |
| 272 static const Level SEVERE = const Level('SEVERE', 1000); | 274 static const Level SEVERE = const Level('SEVERE', 1000); |
| 273 | 275 |
| 274 /** Key for extra debugging loudness ([value] = 1200). */ | 276 /** Key for extra debugging loudness ([value] = 1200). */ |
| 275 static const Level SHOUT = const Level('SHOUT', 1200); | 277 static const Level SHOUT = const Level('SHOUT', 1200); |
| 276 | 278 |
| 277 bool operator ==(Level other) => other != null && value == other.value; | 279 bool operator ==(Object other) => other is Level && value == other.value; |
| 278 bool operator <(Level other) => value < other.value; | 280 bool operator <(Level other) => value < other.value; |
| 279 bool operator <=(Level other) => value <= other.value; | 281 bool operator <=(Level other) => value <= other.value; |
| 280 bool operator >(Level other) => value > other.value; | 282 bool operator >(Level other) => value > other.value; |
| 281 bool operator >=(Level other) => value >= other.value; | 283 bool operator >=(Level other) => value >= other.value; |
| 282 int compareTo(Level other) => value - other.value; | 284 int compareTo(Level other) => value - other.value; |
| 283 int get hashCode => value; | 285 int get hashCode => value; |
| 284 String toString() => name; | 286 String toString() => name; |
| 285 } | 287 } |
| 286 | 288 |
| 287 | 289 |
| 288 /** | 290 /** |
| 289 * A log entry representation used to propagate information from [Logger] to | 291 * A log entry representation used to propagate information from [Logger] to |
| 290 * individual [Handler]s. | 292 * individual [Handler]s. |
| 291 */ | 293 */ |
| 292 class LogRecord { | 294 class LogRecord { |
| 293 final Level level; | 295 final Level level; |
| 294 final String message; | 296 final String message; |
| 295 | 297 |
| 296 /** Logger where this record is stored. */ | 298 /** Logger where this record is stored. */ |
| 297 final String loggerName; | 299 final String loggerName; |
| 298 | 300 |
| 299 /** Time when this record was created. */ | 301 /** Time when this record was created. */ |
| 300 final DateTime time; | 302 final DateTime time; |
| 301 | 303 |
| 302 /** Unique sequence number greater than all log records created before it. */ | 304 /** Unique sequence number greater than all log records created before it. */ |
| 303 final int sequenceNumber; | 305 final int sequenceNumber; |
| 304 | 306 |
| 305 static int _nextNumber = 0; | 307 static int _nextNumber = 0; |
| 306 | 308 |
| 307 /** Associated exception (if any) when recording errors messages. */ | 309 /** Associated error (if any) when recording errors messages. */ |
| 308 var exception; | 310 final Object error; |
| 309 | 311 |
| 310 LogRecord(this.level, this.message, this.loggerName, [this.exception]) | 312 // TODO(kevmoo) - remove before V1 |
| 313 /** |
| 314 * DEPRECATED. Use [error] instead. |
| 315 */ |
| 316 @deprecated |
| 317 Object get exception => error; |
| 318 |
| 319 /** Associated stackTrace (if any) when recording errors messages. */ |
| 320 final StackTrace stackTrace; |
| 321 |
| 322 LogRecord(this.level, this.message, this.loggerName, [this.error, |
| 323 this.stackTrace]) |
| 311 : time = new DateTime.now(), | 324 : time = new DateTime.now(), |
| 312 sequenceNumber = LogRecord._nextNumber++; | 325 sequenceNumber = LogRecord._nextNumber++; |
| 313 | 326 |
| 314 String toString() => '[${level.name}] $loggerName: $message'; | 327 String toString() => '[${level.name}] $loggerName: $message'; |
| 315 } | 328 } |
| OLD | NEW |