| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 */ | |
| 7 library logging; | |
| 8 | |
| 9 import 'dart:async'; | |
| 10 import 'dart:collection'; | |
| 11 | |
| 12 /** | |
| 13 * Whether to allow fine-grain logging and configuration of loggers in a | |
| 14 * hierarchy. When false, all logging is merged in the root logger. | |
| 15 */ | |
| 16 bool hierarchicalLoggingEnabled = false; | |
| 17 | |
| 18 /** | |
| 19 * Automatically record stack traces for any message of this level or above. | |
| 20 * Because this is expensive, this is off by default. | |
| 21 */ | |
| 22 Level recordStackTraceAtLevel = Level.OFF; | |
| 23 | |
| 24 /** | |
| 25 * Level for the root-logger. This will be the level of all loggers if | |
| 26 * [hierarchicalLoggingEnabled] is false. | |
| 27 */ | |
| 28 Level _rootLevel = Level.INFO; | |
| 29 | |
| 30 /** | |
| 31 * Use a [Logger] to log debug messages. [Logger]s are named using a | |
| 32 * hierarchical dot-separated name convention. | |
| 33 */ | |
| 34 class Logger { | |
| 35 /** Simple name of this logger. */ | |
| 36 final String name; | |
| 37 | |
| 38 /** The full name of this logger, which includes the parent's full name. */ | |
| 39 String get fullName => | |
| 40 (parent == null || parent.name == '') ? name : '${parent.fullName}.$name'; | |
| 41 | |
| 42 /** Parent of this logger in the hierarchy of loggers. */ | |
| 43 final Logger parent; | |
| 44 | |
| 45 /** Logging [Level] used for entries generated on this logger. */ | |
| 46 Level _level; | |
| 47 | |
| 48 final Map<String, Logger> _children; | |
| 49 | |
| 50 /** Children in the hierarchy of loggers, indexed by their simple names. */ | |
| 51 final Map<String, Logger> children; | |
| 52 | |
| 53 /** Controller used to notify when log entries are added to this logger. */ | |
| 54 StreamController<LogRecord> _controller; | |
| 55 | |
| 56 /** | |
| 57 * Singleton constructor. Calling `new Logger(name)` will return the same | |
| 58 * actual instance whenever it is called with the same string name. | |
| 59 */ | |
| 60 factory Logger(String name) { | |
| 61 return _loggers.putIfAbsent(name, () => new Logger._named(name)); | |
| 62 } | |
| 63 | |
| 64 factory Logger._named(String name) { | |
| 65 if (name.startsWith('.')) { | |
| 66 throw new ArgumentError("name shouldn't start with a '.'"); | |
| 67 } | |
| 68 // Split hierarchical names (separated with '.'). | |
| 69 int dot = name.lastIndexOf('.'); | |
| 70 Logger parent = null; | |
| 71 String thisName; | |
| 72 if (dot == -1) { | |
| 73 if (name != '') parent = new Logger(''); | |
| 74 thisName = name; | |
| 75 } else { | |
| 76 parent = new Logger(name.substring(0, dot)); | |
| 77 thisName = name.substring(dot + 1); | |
| 78 } | |
| 79 return new Logger._internal(thisName, parent, new Map<String, Logger>()); | |
| 80 } | |
| 81 | |
| 82 Logger._internal(this.name, this.parent, Map<String, Logger> children) | |
| 83 : this._children = children, | |
| 84 this.children = new UnmodifiableMapView(children) { | |
| 85 if (parent != null) parent._children[name] = this; | |
| 86 } | |
| 87 | |
| 88 /** | |
| 89 * Effective level considering the levels established in this logger's parents | |
| 90 * (when [hierarchicalLoggingEnabled] is true). | |
| 91 */ | |
| 92 Level get level { | |
| 93 if (hierarchicalLoggingEnabled) { | |
| 94 if (_level != null) return _level; | |
| 95 if (parent != null) return parent.level; | |
| 96 } | |
| 97 return _rootLevel; | |
| 98 } | |
| 99 | |
| 100 /** Override the level for this particular [Logger] and its children. */ | |
| 101 void set level(Level value) { | |
| 102 if (hierarchicalLoggingEnabled && parent != null) { | |
| 103 _level = value; | |
| 104 } else { | |
| 105 if (parent != null) { | |
| 106 throw new UnsupportedError( | |
| 107 'Please set "hierarchicalLoggingEnabled" to true if you want to ' | |
| 108 'change the level on a non-root logger.'); | |
| 109 } | |
| 110 _rootLevel = value; | |
| 111 } | |
| 112 } | |
| 113 | |
| 114 /** | |
| 115 * Returns an stream of messages added to this [Logger]. You can listen for | |
| 116 * messages using the standard stream APIs, for instance: | |
| 117 * logger.onRecord.listen((record) { ... }); | |
| 118 */ | |
| 119 Stream<LogRecord> get onRecord => _getStream(); | |
| 120 | |
| 121 void clearListeners() { | |
| 122 if (hierarchicalLoggingEnabled || parent == null) { | |
| 123 if (_controller != null) { | |
| 124 _controller.close(); | |
| 125 _controller = null; | |
| 126 } | |
| 127 } else { | |
| 128 root.clearListeners(); | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 /** Whether a message for [value]'s level is loggable in this logger. */ | |
| 133 bool isLoggable(Level value) => (value >= level); | |
| 134 | |
| 135 /** | |
| 136 * Adds a log record for a [message] at a particular [logLevel] if | |
| 137 * `isLoggable(logLevel)` is true. | |
| 138 * | |
| 139 * Use this method to create log entries for user-defined levels. To record a | |
| 140 * message at a predefined level (e.g. [Level.INFO], [Level.WARNING], etc) you | |
| 141 * can use their specialized methods instead (e.g. [info], [warning], etc). | |
| 142 * | |
| 143 * If [message] is a [Function], it will be lazy evaluated. Additionally, if | |
| 144 * [message] or its evaluated value is not a [String], then 'toString()' will | |
| 145 * be called on it and the result will be logged. | |
| 146 * | |
| 147 * The log record will contain a field for the zone in which this call was | |
| 148 * made. | |
| 149 * This can be advantagous if a log listener wants to handle records of | |
| 150 * different zones differently (e.g. group log records by http-request if each | |
| 151 * http-request handler runs in it's own zone). | |
| 152 */ | |
| 153 void log(Level logLevel, message, | |
| 154 [Object error, StackTrace stackTrace, Zone zone]) { | |
| 155 if (isLoggable(logLevel)) { | |
| 156 if (message is Function) message = message(); | |
| 157 if (message is! String) message = message.toString(); | |
| 158 if (stackTrace == null && logLevel >= recordStackTraceAtLevel) { | |
| 159 try { | |
| 160 throw "autogenerated stack trace for $logLevel $message"; | |
| 161 } catch (e, t) { | |
| 162 stackTrace = t; | |
| 163 if (error == null) error = e; | |
| 164 } | |
| 165 } | |
| 166 if (zone == null) zone = Zone.current; | |
| 167 | |
| 168 var record = | |
| 169 new LogRecord(logLevel, message, fullName, error, stackTrace, zone); | |
| 170 | |
| 171 if (hierarchicalLoggingEnabled) { | |
| 172 var target = this; | |
| 173 while (target != null) { | |
| 174 target._publish(record); | |
| 175 target = target.parent; | |
| 176 } | |
| 177 } else { | |
| 178 root._publish(record); | |
| 179 } | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 /** Log message at level [Level.FINEST]. */ | |
| 184 void finest(message, [Object error, StackTrace stackTrace]) => | |
| 185 log(Level.FINEST, message, error, stackTrace); | |
| 186 | |
| 187 /** Log message at level [Level.FINER]. */ | |
| 188 void finer(message, [Object error, StackTrace stackTrace]) => | |
| 189 log(Level.FINER, message, error, stackTrace); | |
| 190 | |
| 191 /** Log message at level [Level.FINE]. */ | |
| 192 void fine(message, [Object error, StackTrace stackTrace]) => | |
| 193 log(Level.FINE, message, error, stackTrace); | |
| 194 | |
| 195 /** Log message at level [Level.CONFIG]. */ | |
| 196 void config(message, [Object error, StackTrace stackTrace]) => | |
| 197 log(Level.CONFIG, message, error, stackTrace); | |
| 198 | |
| 199 /** Log message at level [Level.INFO]. */ | |
| 200 void info(message, [Object error, StackTrace stackTrace]) => | |
| 201 log(Level.INFO, message, error, stackTrace); | |
| 202 | |
| 203 /** Log message at level [Level.WARNING]. */ | |
| 204 void warning(message, [Object error, StackTrace stackTrace]) => | |
| 205 log(Level.WARNING, message, error, stackTrace); | |
| 206 | |
| 207 /** Log message at level [Level.SEVERE]. */ | |
| 208 void severe(message, [Object error, StackTrace stackTrace]) => | |
| 209 log(Level.SEVERE, message, error, stackTrace); | |
| 210 | |
| 211 /** Log message at level [Level.SHOUT]. */ | |
| 212 void shout(message, [Object error, StackTrace stackTrace]) => | |
| 213 log(Level.SHOUT, message, error, stackTrace); | |
| 214 | |
| 215 Stream<LogRecord> _getStream() { | |
| 216 if (hierarchicalLoggingEnabled || parent == null) { | |
| 217 if (_controller == null) { | |
| 218 _controller = new StreamController<LogRecord>.broadcast(sync: true); | |
| 219 } | |
| 220 return _controller.stream; | |
| 221 } else { | |
| 222 return root._getStream(); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 void _publish(LogRecord record) { | |
| 227 if (_controller != null) { | |
| 228 _controller.add(record); | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 /** Top-level root [Logger]. */ | |
| 233 static final Logger root = new Logger(''); | |
| 234 | |
| 235 /** All [Logger]s in the system. */ | |
| 236 static final Map<String, Logger> _loggers = <String, Logger>{}; | |
| 237 } | |
| 238 | |
| 239 /** Handler callback to process log entries as they are added to a [Logger]. */ | |
| 240 typedef void LoggerHandler(LogRecord); | |
| 241 | |
| 242 /** | |
| 243 * [Level]s to control logging output. Logging can be enabled to include all | |
| 244 * levels above certain [Level]. [Level]s are ordered using an integer | |
| 245 * value [Level.value]. The predefined [Level] constants below are sorted as | |
| 246 * follows (in descending order): [Level.SHOUT], [Level.SEVERE], | |
| 247 * [Level.WARNING], [Level.INFO], [Level.CONFIG], [Level.FINE], [Level.FINER], | |
| 248 * [Level.FINEST], and [Level.ALL]. | |
| 249 * | |
| 250 * We recommend using one of the predefined logging levels. If you define your | |
| 251 * own level, make sure you use a value between those used in [Level.ALL] and | |
| 252 * [Level.OFF]. | |
| 253 */ | |
| 254 class Level implements Comparable<Level> { | |
| 255 final String name; | |
| 256 | |
| 257 /** | |
| 258 * Unique value for this level. Used to order levels, so filtering can exclude | |
| 259 * messages whose level is under certain value. | |
| 260 */ | |
| 261 final int value; | |
| 262 | |
| 263 const Level(this.name, this.value); | |
| 264 | |
| 265 /** Special key to turn on logging for all levels ([value] = 0). */ | |
| 266 static const Level ALL = const Level('ALL', 0); | |
| 267 | |
| 268 /** Special key to turn off all logging ([value] = 2000). */ | |
| 269 static const Level OFF = const Level('OFF', 2000); | |
| 270 | |
| 271 /** Key for highly detailed tracing ([value] = 300). */ | |
| 272 static const Level FINEST = const Level('FINEST', 300); | |
| 273 | |
| 274 /** Key for fairly detailed tracing ([value] = 400). */ | |
| 275 static const Level FINER = const Level('FINER', 400); | |
| 276 | |
| 277 /** Key for tracing information ([value] = 500). */ | |
| 278 static const Level FINE = const Level('FINE', 500); | |
| 279 | |
| 280 /** Key for static configuration messages ([value] = 700). */ | |
| 281 static const Level CONFIG = const Level('CONFIG', 700); | |
| 282 | |
| 283 /** Key for informational messages ([value] = 800). */ | |
| 284 static const Level INFO = const Level('INFO', 800); | |
| 285 | |
| 286 /** Key for potential problems ([value] = 900). */ | |
| 287 static const Level WARNING = const Level('WARNING', 900); | |
| 288 | |
| 289 /** Key for serious failures ([value] = 1000). */ | |
| 290 static const Level SEVERE = const Level('SEVERE', 1000); | |
| 291 | |
| 292 /** Key for extra debugging loudness ([value] = 1200). */ | |
| 293 static const Level SHOUT = const Level('SHOUT', 1200); | |
| 294 | |
| 295 static const List<Level> LEVELS = const [ | |
| 296 ALL, | |
| 297 FINEST, | |
| 298 FINER, | |
| 299 FINE, | |
| 300 CONFIG, | |
| 301 INFO, | |
| 302 WARNING, | |
| 303 SEVERE, | |
| 304 SHOUT, | |
| 305 OFF | |
| 306 ]; | |
| 307 | |
| 308 bool operator ==(other) => other is Level && value == other.value; | |
| 309 bool operator <(Level other) => value < other.value; | |
| 310 bool operator <=(Level other) => value <= other.value; | |
| 311 bool operator >(Level other) => value > other.value; | |
| 312 bool operator >=(Level other) => value >= other.value; | |
| 313 int compareTo(Level other) => value - other.value; | |
| 314 int get hashCode => value; | |
| 315 String toString() => name; | |
| 316 } | |
| 317 | |
| 318 /** | |
| 319 * A log entry representation used to propagate information from [Logger] to | |
| 320 * individual [Handler]s. | |
| 321 */ | |
| 322 class LogRecord { | |
| 323 final Level level; | |
| 324 final String message; | |
| 325 | |
| 326 /** Logger where this record is stored. */ | |
| 327 final String loggerName; | |
| 328 | |
| 329 /** Time when this record was created. */ | |
| 330 final DateTime time; | |
| 331 | |
| 332 /** Unique sequence number greater than all log records created before it. */ | |
| 333 final int sequenceNumber; | |
| 334 | |
| 335 static int _nextNumber = 0; | |
| 336 | |
| 337 /** Associated error (if any) when recording errors messages. */ | |
| 338 final Object error; | |
| 339 | |
| 340 /** Associated stackTrace (if any) when recording errors messages. */ | |
| 341 final StackTrace stackTrace; | |
| 342 | |
| 343 /** Zone of the calling code which resulted in this LogRecord. */ | |
| 344 final Zone zone; | |
| 345 | |
| 346 LogRecord(this.level, this.message, this.loggerName, | |
| 347 [this.error, this.stackTrace, this.zone]) | |
| 348 : time = new DateTime.now(), | |
| 349 sequenceNumber = LogRecord._nextNumber++; | |
| 350 | |
| 351 String toString() => '[${level.name}] $loggerName: $message'; | |
| 352 } | |
| OLD | NEW |