|
2 | 2 | // for details. All rights reserved. Use of this source code is governed by a
|
3 | 3 | // BSD-style license that can be found in the LICENSE file.
|
4 | 4 |
|
5 |
| -library logging; |
| 5 | +import 'src/log_record.dart'; |
| 6 | +import 'src/logger.dart'; |
6 | 7 |
|
7 |
| -import 'dart:async'; |
8 |
| -import 'dart:collection'; |
9 |
| - |
10 |
| -/// Whether to allow fine-grain logging and configuration of loggers in a |
11 |
| -/// hierarchy. |
12 |
| -/// |
13 |
| -/// When false, all logging is merged in the root logger. |
14 |
| -bool hierarchicalLoggingEnabled = false; |
15 |
| - |
16 |
| -/// Automatically record stack traces for any message of this level or above. |
17 |
| -/// |
18 |
| -/// Because this is expensive, this is off by default. |
19 |
| -Level recordStackTraceAtLevel = Level.OFF; |
20 |
| - |
21 |
| -/// Level for the root-logger. |
22 |
| -/// |
23 |
| -/// This will be the level of all loggers if [hierarchicalLoggingEnabled] is |
24 |
| -/// false. |
25 |
| -Level _rootLevel = Level.INFO; |
26 |
| - |
27 |
| -/// Use a [Logger] to log debug messages. |
28 |
| -/// |
29 |
| -/// [Logger]s are named using a hierarchical dot-separated name convention. |
30 |
| -class Logger { |
31 |
| - /// Simple name of this logger. |
32 |
| - final String name; |
33 |
| - |
34 |
| - /// The full name of this logger, which includes the parent's full name. |
35 |
| - String get fullName => |
36 |
| - (parent == null || parent.name == '') ? name : '${parent.fullName}.$name'; |
37 |
| - |
38 |
| - /// Parent of this logger in the hierarchy of loggers. |
39 |
| - final Logger parent; |
40 |
| - |
41 |
| - /// Logging [Level] used for entries generated on this logger. |
42 |
| - Level _level; |
43 |
| - |
44 |
| - final Map<String, Logger> _children; |
45 |
| - |
46 |
| - /// Children in the hierarchy of loggers, indexed by their simple names. |
47 |
| - final Map<String, Logger> children; |
48 |
| - |
49 |
| - /// Controller used to notify when log entries are added to this logger. |
50 |
| - StreamController<LogRecord> _controller; |
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 |
| - factory Logger(String name) => |
55 |
| - _loggers.putIfAbsent(name, () => Logger._named(name)); |
56 |
| - |
57 |
| - /// Creates a new detached [Logger]. |
58 |
| - /// |
59 |
| - /// Returns a new [Logger] instance (unlike `new Logger`, which returns a |
60 |
| - /// [Logger] singleton), which doesn't have any parent or children, |
61 |
| - /// and is not a part of the global hierarchical loggers structure. |
62 |
| - /// |
63 |
| - /// It can be useful when you just need a local short-living logger, |
64 |
| - /// which you'd like to be garbage-collected later. |
65 |
| - factory Logger.detached(String name) => |
66 |
| - Logger._internal(name, null, <String, Logger>{}); |
67 |
| - |
68 |
| - factory Logger._named(String name) { |
69 |
| - if (name.startsWith('.')) { |
70 |
| - throw ArgumentError("name shouldn't start with a '.'"); |
71 |
| - } |
72 |
| - // Split hierarchical names (separated with '.'). |
73 |
| - var dot = name.lastIndexOf('.'); |
74 |
| - Logger parent; |
75 |
| - String thisName; |
76 |
| - if (dot == -1) { |
77 |
| - if (name != '') parent = Logger(''); |
78 |
| - thisName = name; |
79 |
| - } else { |
80 |
| - parent = Logger(name.substring(0, dot)); |
81 |
| - thisName = name.substring(dot + 1); |
82 |
| - } |
83 |
| - return Logger._internal(thisName, parent, <String, Logger>{}); |
84 |
| - } |
85 |
| - |
86 |
| - Logger._internal(this.name, this.parent, Map<String, Logger> children) |
87 |
| - : _children = children, |
88 |
| - children = UnmodifiableMapView(children) { |
89 |
| - if (parent != null) parent._children[name] = this; |
90 |
| - } |
91 |
| - |
92 |
| - /// Effective level considering the levels established in this logger's |
93 |
| - /// parents (when [hierarchicalLoggingEnabled] is true). |
94 |
| - Level get level { |
95 |
| - if (hierarchicalLoggingEnabled) { |
96 |
| - if (_level != null) return _level; |
97 |
| - if (parent != null) return parent.level; |
98 |
| - } |
99 |
| - return _rootLevel; |
100 |
| - } |
101 |
| - |
102 |
| - /// Override the level for this particular [Logger] and its children. |
103 |
| - set level(Level value) { |
104 |
| - if (hierarchicalLoggingEnabled && parent != null) { |
105 |
| - _level = value; |
106 |
| - } else { |
107 |
| - if (parent != null) { |
108 |
| - throw UnsupportedError( |
109 |
| - 'Please set "hierarchicalLoggingEnabled" to true if you want to ' |
110 |
| - 'change the level on a non-root logger.'); |
111 |
| - } |
112 |
| - _rootLevel = value; |
113 |
| - } |
114 |
| - } |
115 |
| - |
116 |
| - /// Returns a stream of messages added to this [Logger]. |
117 |
| - /// |
118 |
| - /// You can listen for messages using the standard stream APIs, for instance: |
119 |
| - /// |
120 |
| - /// ```dart |
121 |
| - /// logger.onRecord.listen((record) { ... }); |
122 |
| - /// ``` |
123 |
| - Stream<LogRecord> get onRecord => _getStream(); |
124 |
| - |
125 |
| - void clearListeners() { |
126 |
| - if (hierarchicalLoggingEnabled || parent == null) { |
127 |
| - if (_controller != null) { |
128 |
| - _controller.close(); |
129 |
| - _controller = null; |
130 |
| - } |
131 |
| - } else { |
132 |
| - root.clearListeners(); |
133 |
| - } |
134 |
| - } |
135 |
| - |
136 |
| - /// Whether a message for [value]'s level is loggable in this logger. |
137 |
| - bool isLoggable(Level value) => (value >= level); |
138 |
| - |
139 |
| - /// Adds a log record for a [message] at a particular [logLevel] if |
140 |
| - /// `isLoggable(logLevel)` is true. |
141 |
| - /// |
142 |
| - /// Use this method to create log entries for user-defined levels. To record a |
143 |
| - /// message at a predefined level (e.g. [Level.INFO], [Level.WARNING], etc) |
144 |
| - /// you can use their specialized methods instead (e.g. [info], [warning], |
145 |
| - /// etc). |
146 |
| - /// |
147 |
| - /// If [message] is a [Function], it will be lazy evaluated. Additionally, if |
148 |
| - /// [message] or its evaluated value is not a [String], then 'toString()' will |
149 |
| - /// be called on the object and the result will be logged. The log record will |
150 |
| - /// contain a field holding the original object. |
151 |
| - /// |
152 |
| - /// The log record will also contain a field for the zone in which this call |
153 |
| - /// was made. This can be advantageous if a log listener wants to handler |
154 |
| - /// records of different zones differently (e.g. group log records by HTTP |
155 |
| - /// request if each HTTP request handler runs in it's own zone). |
156 |
| - void log(Level logLevel, message, |
157 |
| - [Object error, StackTrace stackTrace, Zone zone]) { |
158 |
| - Object object; |
159 |
| - if (isLoggable(logLevel)) { |
160 |
| - if (message is Function) { |
161 |
| - message = message(); |
162 |
| - } |
163 |
| - |
164 |
| - String msg; |
165 |
| - if (message is String) { |
166 |
| - msg = message; |
167 |
| - } else { |
168 |
| - msg = message.toString(); |
169 |
| - object = message; |
170 |
| - } |
171 |
| - |
172 |
| - if (stackTrace == null && logLevel >= recordStackTraceAtLevel) { |
173 |
| - stackTrace = StackTrace.current; |
174 |
| - error ??= 'autogenerated stack trace for $logLevel $msg'; |
175 |
| - } |
176 |
| - zone ??= Zone.current; |
177 |
| - |
178 |
| - var record = |
179 |
| - LogRecord(logLevel, msg, fullName, error, stackTrace, zone, object); |
180 |
| - |
181 |
| - if (hierarchicalLoggingEnabled) { |
182 |
| - var target = this; |
183 |
| - while (target != null) { |
184 |
| - target._publish(record); |
185 |
| - target = target.parent; |
186 |
| - } |
187 |
| - } else { |
188 |
| - root._publish(record); |
189 |
| - } |
190 |
| - } |
191 |
| - } |
192 |
| - |
193 |
| - /// Log message at level [Level.FINEST]. |
194 |
| - void finest(message, [Object error, StackTrace stackTrace]) => |
195 |
| - log(Level.FINEST, message, error, stackTrace); |
196 |
| - |
197 |
| - /// Log message at level [Level.FINER]. |
198 |
| - void finer(message, [Object error, StackTrace stackTrace]) => |
199 |
| - log(Level.FINER, message, error, stackTrace); |
200 |
| - |
201 |
| - /// Log message at level [Level.FINE]. |
202 |
| - void fine(message, [Object error, StackTrace stackTrace]) => |
203 |
| - log(Level.FINE, message, error, stackTrace); |
204 |
| - |
205 |
| - /// Log message at level [Level.CONFIG]. |
206 |
| - void config(message, [Object error, StackTrace stackTrace]) => |
207 |
| - log(Level.CONFIG, message, error, stackTrace); |
208 |
| - |
209 |
| - /// Log message at level [Level.INFO]. |
210 |
| - void info(message, [Object error, StackTrace stackTrace]) => |
211 |
| - log(Level.INFO, message, error, stackTrace); |
212 |
| - |
213 |
| - /// Log message at level [Level.WARNING]. |
214 |
| - void warning(message, [Object error, StackTrace stackTrace]) => |
215 |
| - log(Level.WARNING, message, error, stackTrace); |
216 |
| - |
217 |
| - /// Log message at level [Level.SEVERE]. |
218 |
| - void severe(message, [Object error, StackTrace stackTrace]) => |
219 |
| - log(Level.SEVERE, message, error, stackTrace); |
220 |
| - |
221 |
| - /// Log message at level [Level.SHOUT]. |
222 |
| - void shout(message, [Object error, StackTrace stackTrace]) => |
223 |
| - log(Level.SHOUT, message, error, stackTrace); |
224 |
| - |
225 |
| - Stream<LogRecord> _getStream() { |
226 |
| - if (hierarchicalLoggingEnabled || parent == null) { |
227 |
| - _controller ??= StreamController<LogRecord>.broadcast(sync: true); |
228 |
| - return _controller.stream; |
229 |
| - } else { |
230 |
| - return root._getStream(); |
231 |
| - } |
232 |
| - } |
233 |
| - |
234 |
| - void _publish(LogRecord record) { |
235 |
| - if (_controller != null) { |
236 |
| - _controller.add(record); |
237 |
| - } |
238 |
| - } |
239 |
| - |
240 |
| - /// Top-level root [Logger]. |
241 |
| - static final Logger root = Logger(''); |
242 |
| - |
243 |
| - /// All [Logger]s in the system. |
244 |
| - static final Map<String, Logger> _loggers = <String, Logger>{}; |
245 |
| -} |
| 8 | +export 'src/level.dart'; |
| 9 | +export 'src/log_record.dart'; |
| 10 | +export 'src/logger.dart'; |
246 | 11 |
|
247 | 12 | /// Handler callback to process log entries as they are added to a [Logger].
|
248 |
| -@deprecated |
| 13 | +@Deprecated('Will be removed in 1.0.0') |
249 | 14 | typedef LoggerHandler = void Function(LogRecord record);
|
250 |
| - |
251 |
| -/// [Level]s to control logging output. Logging can be enabled to include all |
252 |
| -/// levels above certain [Level]. [Level]s are ordered using an integer |
253 |
| -/// value [Level.value]. The predefined [Level] constants below are sorted as |
254 |
| -/// follows (in descending order): [Level.SHOUT], [Level.SEVERE], |
255 |
| -/// [Level.WARNING], [Level.INFO], [Level.CONFIG], [Level.FINE], [Level.FINER], |
256 |
| -/// [Level.FINEST], and [Level.ALL]. |
257 |
| -/// |
258 |
| -/// We recommend using one of the predefined logging levels. If you define your |
259 |
| -/// own level, make sure you use a value between those used in [Level.ALL] and |
260 |
| -/// [Level.OFF]. |
261 |
| -class Level implements Comparable<Level> { |
262 |
| - final String name; |
263 |
| - |
264 |
| - /// Unique value for this level. Used to order levels, so filtering can |
265 |
| - /// exclude messages whose level is under certain value. |
266 |
| - final int value; |
267 |
| - |
268 |
| - const Level(this.name, this.value); |
269 |
| - |
270 |
| - /// Special key to turn on logging for all levels ([value] = 0). |
271 |
| - static const Level ALL = Level('ALL', 0); |
272 |
| - |
273 |
| - /// Special key to turn off all logging ([value] = 2000). |
274 |
| - static const Level OFF = Level('OFF', 2000); |
275 |
| - |
276 |
| - /// Key for highly detailed tracing ([value] = 300). |
277 |
| - static const Level FINEST = Level('FINEST', 300); |
278 |
| - |
279 |
| - /// Key for fairly detailed tracing ([value] = 400). |
280 |
| - static const Level FINER = Level('FINER', 400); |
281 |
| - |
282 |
| - /// Key for tracing information ([value] = 500). |
283 |
| - static const Level FINE = Level('FINE', 500); |
284 |
| - |
285 |
| - /// Key for static configuration messages ([value] = 700). |
286 |
| - static const Level CONFIG = Level('CONFIG', 700); |
287 |
| - |
288 |
| - /// Key for informational messages ([value] = 800). |
289 |
| - static const Level INFO = Level('INFO', 800); |
290 |
| - |
291 |
| - /// Key for potential problems ([value] = 900). |
292 |
| - static const Level WARNING = Level('WARNING', 900); |
293 |
| - |
294 |
| - /// Key for serious failures ([value] = 1000). |
295 |
| - static const Level SEVERE = Level('SEVERE', 1000); |
296 |
| - |
297 |
| - /// Key for extra debugging loudness ([value] = 1200). |
298 |
| - static const Level SHOUT = Level('SHOUT', 1200); |
299 |
| - |
300 |
| - static const List<Level> LEVELS = [ |
301 |
| - ALL, |
302 |
| - FINEST, |
303 |
| - FINER, |
304 |
| - FINE, |
305 |
| - CONFIG, |
306 |
| - INFO, |
307 |
| - WARNING, |
308 |
| - SEVERE, |
309 |
| - SHOUT, |
310 |
| - OFF |
311 |
| - ]; |
312 |
| - |
313 |
| - @override |
314 |
| - bool operator ==(Object other) => other is Level && value == other.value; |
315 |
| - bool operator <(Level other) => value < other.value; |
316 |
| - bool operator <=(Level other) => value <= other.value; |
317 |
| - bool operator >(Level other) => value > other.value; |
318 |
| - bool operator >=(Level other) => value >= other.value; |
319 |
| - |
320 |
| - @override |
321 |
| - int compareTo(Level other) => value - other.value; |
322 |
| - |
323 |
| - @override |
324 |
| - int get hashCode => value; |
325 |
| - |
326 |
| - @override |
327 |
| - String toString() => name; |
328 |
| -} |
329 |
| - |
330 |
| -/// A log entry representation used to propagate information from [Logger] to |
331 |
| -/// individual handlers. |
332 |
| -class LogRecord { |
333 |
| - final Level level; |
334 |
| - final String message; |
335 |
| - |
336 |
| - /// Non-string message passed to Logger. |
337 |
| - final Object object; |
338 |
| - |
339 |
| - /// Logger where this record is stored. |
340 |
| - final String loggerName; |
341 |
| - |
342 |
| - /// Time when this record was created. |
343 |
| - final DateTime time; |
344 |
| - |
345 |
| - /// Unique sequence number greater than all log records created before it. |
346 |
| - final int sequenceNumber; |
347 |
| - |
348 |
| - static int _nextNumber = 0; |
349 |
| - |
350 |
| - /// Associated error (if any) when recording errors messages. |
351 |
| - final Object error; |
352 |
| - |
353 |
| - /// Associated stackTrace (if any) when recording errors messages. |
354 |
| - final StackTrace stackTrace; |
355 |
| - |
356 |
| - /// Zone of the calling code which resulted in this LogRecord. |
357 |
| - final Zone zone; |
358 |
| - |
359 |
| - LogRecord(this.level, this.message, this.loggerName, |
360 |
| - [this.error, this.stackTrace, this.zone, this.object]) |
361 |
| - : time = DateTime.now(), |
362 |
| - sequenceNumber = LogRecord._nextNumber++; |
363 |
| - |
364 |
| - @override |
365 |
| - String toString() => '[${level.name}] $loggerName: $message'; |
366 |
| -} |
0 commit comments