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 |