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