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 |