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 |