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 |