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