561 lines, 320 LOC, 244 covered (76%)
38 | 1 | /* ***** BEGIN LICENSE BLOCK ***** |
2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
|
3 | * |
|
4 | * The contents of this file are subject to the Mozilla Public License Version |
|
5 | * 1.1 (the "License"); you may not use this file except in compliance with |
|
6 | * the License. You may obtain a copy of the License at |
|
7 | * http://www.mozilla.org/MPL/ |
|
8 | * |
|
9 | * Software distributed under the License is distributed on an "AS IS" basis, |
|
10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
|
11 | * for the specific language governing rights and limitations under the |
|
12 | * License. |
|
13 | * |
|
14 | * The Original Code is log4moz |
|
15 | * |
|
16 | * The Initial Developer of the Original Code is |
|
17 | * Michael Johnston |
|
18 | * Portions created by the Initial Developer are Copyright (C) 2006 |
|
19 | * the Initial Developer. All Rights Reserved. |
|
20 | * |
|
21 | * Contributor(s): |
|
22 | * Michael Johnston <special.michael@gmail.com> |
|
23 | * Dan Mills <thunder@mozilla.com> |
|
24 | * |
|
25 | * Alternatively, the contents of this file may be used under the terms of |
|
26 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
|
27 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
|
28 | * in which case the provisions of the GPL or the LGPL are applicable instead |
|
29 | * of those above. If you wish to allow use of your version of this file only |
|
30 | * under the terms of either the GPL or the LGPL, and not to allow others to |
|
31 | * use your version of this file under the terms of the MPL, indicate your |
|
32 | * decision by deleting the provisions above and replace them with the notice |
|
33 | * and other provisions required by the GPL or the LGPL. If you do not delete |
|
34 | * the provisions above, a recipient may use your version of this file under |
|
35 | * the terms of any one of the MPL, the GPL or the LGPL. |
|
36 | * |
|
37 | * ***** END LICENSE BLOCK ***** */ |
|
38 | ||
266 | 39 | const EXPORTED_SYMBOLS = ['Log4Moz']; |
40 | ||
152 | 41 | const Cc = Components.classes; |
152 | 42 | const Ci = Components.interfaces; |
152 | 43 | const Cr = Components.results; |
152 | 44 | const Cu = Components.utils; |
45 | ||
190 | 46 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
47 | ||
114 | 48 | const MODE_RDONLY = 0x01; |
114 | 49 | const MODE_WRONLY = 0x02; |
114 | 50 | const MODE_CREATE = 0x08; |
114 | 51 | const MODE_APPEND = 0x10; |
114 | 52 | const MODE_TRUNCATE = 0x20; |
53 | ||
114 | 54 | const PERMS_FILE = 0644; |
114 | 55 | const PERMS_DIRECTORY = 0755; |
56 | ||
114 | 57 | const ONE_BYTE = 1; |
190 | 58 | const ONE_KILOBYTE = 1024 * ONE_BYTE; |
190 | 59 | const ONE_MEGABYTE = 1024 * ONE_KILOBYTE; |
60 | ||
76 | 61 | let Log4Moz = { |
38 | 62 | Level: { |
76 | 63 | Fatal: 70, |
76 | 64 | Error: 60, |
76 | 65 | Warn: 50, |
76 | 66 | Info: 40, |
76 | 67 | Config: 30, |
76 | 68 | Debug: 20, |
76 | 69 | Trace: 10, |
76 | 70 | All: 0, |
76 | 71 | Desc: { |
114 | 72 | 70: "FATAL", |
114 | 73 | 60: "ERROR", |
114 | 74 | 50: "WARN", |
114 | 75 | 40: "INFO", |
114 | 76 | 30: "CONFIG", |
114 | 77 | 20: "DEBUG", |
114 | 78 | 10: "TRACE", |
228 | 79 | 0: "ALL" |
80 | } |
|
81 | }, |
|
82 | ||
97 | 83 | get repository() { |
63 | 84 | delete Log4Moz.repository; |
84 | 85 | Log4Moz.repository = new LoggerRepository(); |
63 | 86 | return Log4Moz.repository; |
87 | }, |
|
76 | 88 | set repository(value) { |
89 | delete Log4Moz.repository; |
|
90 | Log4Moz.repository = value; |
|
91 | }, |
|
92 | ||
76 | 93 | get LogMessage() { return LogMessage; }, |
76 | 94 | get Logger() { return Logger; }, |
76 | 95 | get LoggerRepository() { return LoggerRepository; }, |
96 | ||
130 | 97 | get Formatter() { return Formatter; }, |
88 | 98 | get BasicFormatter() { return BasicFormatter; }, |
99 | ||
79 | 100 | get Appender() { return Appender; }, |
151 | 101 | get DumpAppender() { return DumpAppender; }, |
82 | 102 | get ConsoleAppender() { return ConsoleAppender; }, |
76 | 103 | get FileAppender() { return FileAppender; }, |
82 | 104 | get RotatingFileAppender() { return RotatingFileAppender; }, |
105 | ||
106 | // Logging helper: |
|
107 | // let logger = Log4Moz.repository.getLogger("foo"); |
|
108 | // logger.info(Log4Moz.enumerateInterfaces(someObject).join(",")); |
|
76 | 109 | enumerateInterfaces: function Log4Moz_enumerateInterfaces(aObject) { |
110 | let interfaces = []; |
|
111 | ||
112 | for (i in Ci) { |
|
113 | try { |
|
114 | aObject.QueryInterface(Ci[i]); |
|
115 | interfaces.push(i); |
|
116 | } |
|
117 | catch(ex) {} |
|
118 | } |
|
119 | ||
120 | return interfaces; |
|
121 | }, |
|
122 | ||
123 | // Logging helper: |
|
124 | // let logger = Log4Moz.repository.getLogger("foo"); |
|
125 | // logger.info(Log4Moz.enumerateProperties(someObject).join(",")); |
|
152 | 126 | enumerateProperties: function Log4Moz_enumerateProps(aObject, |
127 | aExcludeComplexTypes) { |
|
128 | let properties = []; |
|
129 | ||
130 | for (p in aObject) { |
|
131 | try { |
|
132 | if (aExcludeComplexTypes && |
|
133 | (typeof aObject[p] == "object" || typeof aObject[p] == "function")) |
|
134 | continue; |
|
135 | properties.push(p + " = " + aObject[p]); |
|
136 | } |
|
137 | catch(ex) { |
|
138 | properties.push(p + " = " + ex); |
|
139 | } |
|
140 | } |
|
141 | ||
142 | return properties; |
|
143 | } |
|
144 | }; |
|
145 | ||
146 | ||
147 | /* |
|
148 | * LogMessage |
|
149 | * Encapsulates a single log event's data |
|
150 | */ |
|
9049 | 151 | function LogMessage(loggerName, level, message){ |
26919 | 152 | this.loggerName = loggerName; |
26919 | 153 | this.message = message; |
26919 | 154 | this.level = level; |
53838 | 155 | this.time = Date.now(); |
156 | } |
|
76 | 157 | LogMessage.prototype = { |
380 | 158 | QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), |
159 | ||
973 | 160 | get levelDesc() { |
4485 | 161 | if (this.level in Log4Moz.Level.Desc) |
5382 | 162 | return Log4Moz.Level.Desc[this.level]; |
163 | return "UNKNOWN"; |
|
164 | }, |
|
165 | ||
190 | 166 | toString: function LogMsg_toString(){ |
167 | return "LogMessage [" + this.time + " " + this.level + " " + |
|
168 | this.message + "]"; |
|
169 | } |
|
170 | }; |
|
171 | ||
172 | /* |
|
173 | * Logger |
|
174 | * Hierarchical version. Logs to all appenders, assigned or inherited |
|
175 | */ |
|
176 | ||
224 | 177 | function Logger(name, repository) { |
444 | 178 | if (!repository) |
179 | repository = Log4Moz.repository; |
|
444 | 180 | this._name = name; |
444 | 181 | this._appenders = []; |
592 | 182 | this._repository = repository; |
183 | } |
|
76 | 184 | Logger.prototype = { |
380 | 185 | QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), |
186 | ||
76 | 187 | parent: null, |
188 | ||
78 | 189 | get name() { |
4 | 190 | return this._name; |
191 | }, |
|
192 | ||
76 | 193 | _level: null, |
13601 | 194 | get level() { |
40575 | 195 | if (this._level != null) |
25462 | 196 | return this._level; |
1588 | 197 | if (this.parent) |
3176 | 198 | return this.parent.level; |
199 | dump("log4moz warning: root logger configuration error: no level defined\n"); |
|
200 | return Log4Moz.Level.All; |
|
201 | }, |
|
367 | 202 | set level(level) { |
1164 | 203 | this._level = level; |
204 | }, |
|
205 | ||
76 | 206 | _appenders: null, |
1893 | 207 | get appenders() { |
5451 | 208 | if (!this.parent) |
1818 | 209 | return this._appenders; |
7264 | 210 | return this._appenders.concat(this.parent.appenders); |
211 | }, |
|
212 | ||
89 | 213 | addAppender: function Logger_addAppender(appender) { |
193 | 214 | for (let i = 0; i < this._appenders.length; i++) { |
45 | 215 | if (this._appenders[i] == appender) |
216 | return; |
|
217 | } |
|
91 | 218 | this._appenders.push(appender); |
219 | }, |
|
220 | ||
76 | 221 | removeAppender: function Logger_removeAppender(appender) { |
222 | let newAppenders = []; |
|
223 | for (let i = 0; i < this._appenders.length; i++) { |
|
224 | if (this._appenders[i] != appender) |
|
225 | newAppenders.push(this._appenders[i]); |
|
226 | } |
|
227 | this._appenders = newAppenders; |
|
228 | }, |
|
229 | ||
9049 | 230 | log: function Logger_log(message) { |
26919 | 231 | if (this.level > message.level) |
16128 | 232 | return; |
1818 | 233 | let appenders = this.appenders; |
14097 | 234 | for (let i = 0; i < appenders.length; i++){ |
6902 | 235 | appenders[i].append(message); |
909 | 236 | } |
237 | }, |
|
238 | ||
76 | 239 | fatal: function Logger_fatal(string) { |
240 | this.log(new LogMessage(this._name, Log4Moz.Level.Fatal, string)); |
|
241 | }, |
|
79 | 242 | error: function Logger_error(string) { |
36 | 243 | this.log(new LogMessage(this._name, Log4Moz.Level.Error, string)); |
244 | }, |
|
78 | 245 | warn: function Logger_warn(string) { |
24 | 246 | this.log(new LogMessage(this._name, Log4Moz.Level.Warn, string)); |
247 | }, |
|
243 | 248 | info: function Logger_info(string) { |
2004 | 249 | this.log(new LogMessage(this._name, Log4Moz.Level.Info, string)); |
250 | }, |
|
76 | 251 | config: function Logger_config(string) { |
252 | this.log(new LogMessage(this._name, Log4Moz.Level.Config, string)); |
|
253 | }, |
|
409 | 254 | debug: function Logger_debug(string) { |
3996 | 255 | this.log(new LogMessage(this._name, Log4Moz.Level.Debug, string)); |
256 | }, |
|
8658 | 257 | trace: function Logger_trace(string) { |
101616 | 258 | this.log(new LogMessage(this._name, Log4Moz.Level.Trace, string)); |
259 | } |
|
260 | }; |
|
261 | ||
262 | /* |
|
263 | * LoggerRepository |
|
264 | * Implements a hierarchy of Loggers |
|
265 | */ |
|
266 | ||
118 | 267 | function LoggerRepository() {} |
76 | 268 | LoggerRepository.prototype = { |
380 | 269 | QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), |
270 | ||
114 | 271 | _loggers: {}, |
272 | ||
76 | 273 | _rootLogger: null, |
227 | 274 | get rootLogger() { |
453 | 275 | if (!this._rootLogger) { |
126 | 276 | this._rootLogger = new Logger("root", this); |
105 | 277 | this._rootLogger.level = Log4Moz.Level.All; |
278 | } |
|
302 | 279 | return this._rootLogger; |
280 | }, |
|
281 | // FIXME: need to update all parent values if we do this |
|
282 | //set rootLogger(logger) { |
|
283 | // this._rootLogger = logger; |
|
284 | //}, |
|
285 | ||
204 | 286 | _updateParents: function LogRep__updateParents(name) { |
640 | 287 | let pieces = name.split('.'); |
512 | 288 | let cur, parent; |
289 | ||
290 | // find the closest parent |
|
291 | // don't test for the logger name itself, as there's a chance it's already |
|
292 | // there in this._loggers |
|
2048 | 293 | for (let i = 0; i < pieces.length - 1; i++) { |
160 | 294 | if (cur) |
18 | 295 | cur += '.' + pieces[i]; |
296 | else |
|
312 | 297 | cur = pieces[i]; |
240 | 298 | if (cur in this._loggers) |
8 | 299 | parent = cur; |
300 | } |
|
301 | ||
302 | // if we didn't assign a parent above, there is no parent |
|
384 | 303 | if (!parent) |
750 | 304 | this._loggers[name].parent = this.rootLogger; |
305 | else |
|
21 | 306 | this._loggers[name].parent = this._loggers[parent]; |
307 | ||
308 | // trigger updates for any possible descendants of this logger |
|
2284 | 309 | for (let logger in this._loggers) { |
8220 | 310 | if (logger != name && logger.indexOf(name) == 0) |
2289 | 311 | this._updateParents(logger); |
128 | 312 | } |
313 | }, |
|
314 | ||
704 | 315 | getLogger: function LogRep_getLogger(name) { |
1542 | 316 | if (!name) |
10 | 317 | name = this.getLogger.caller.name; |
1542 | 318 | if (name in this._loggers) |
1548 | 319 | return this._loggers[name]; |
889 | 320 | this._loggers[name] = new Logger(name, this); |
635 | 321 | this._updateParents(name); |
508 | 322 | return this._loggers[name]; |
323 | } |
|
324 | }; |
|
325 | ||
326 | /* |
|
327 | * Formatters |
|
328 | * These massage a LogMessage into whatever output is desired |
|
329 | * Only the BasicFormatter is currently implemented |
|
330 | */ |
|
331 | ||
332 | // Abstract formatter |
|
112 | 333 | function Formatter() {} |
76 | 334 | Formatter.prototype = { |
380 | 335 | QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), |
190 | 336 | format: function Formatter_format(message) {} |
337 | }; |
|
338 | ||
339 | // FIXME: should allow for formatting the whole string, not just the date |
|
86 | 340 | function BasicFormatter(dateFormat) { |
20 | 341 | if (dateFormat) |
10 | 342 | this.dateFormat = dateFormat; |
343 | } |
|
76 | 344 | BasicFormatter.prototype = { |
114 | 345 | __proto__: Formatter.prototype, |
346 | ||
76 | 347 | _dateFormat: null, |
348 | ||
76 | 349 | get dateFormat() { |
350 | if (!this._dateFormat) |
|
351 | this._dateFormat = "%Y-%m-%d %H:%M:%S"; |
|
352 | return this._dateFormat; |
|
353 | }, |
|
354 | ||
76 | 355 | set dateFormat(format) { |
356 | this._dateFormat = format; |
|
357 | }, |
|
358 | ||
312 | 359 | format: function BF_format(message) { |
360 | // Pad a string to a certain length (20) with a character (space) |
|
488 | 361 | let pad = function BF__pad(str, len, chr) str + |
2562 | 362 | new Array(Math.max((len || 20) - str.length + 1, 0)).join(chr || " "); |
363 | ||
364 | // Generate a date string because toLocaleString doesn't work XXX 514803 |
|
4291 | 365 | let z = function(n) n < 10 ? "0" + n : n; |
488 | 366 | let d = new Date(message.time); |
1464 | 367 | let dateStr = [d.getFullYear(), "-", z(d.getMonth() + 1), "-", |
2196 | 368 | z(d.getDate()), " ", z(d.getHours()), ":", z(d.getMinutes()), ":", |
1220 | 369 | z(d.getSeconds())].join(""); |
370 | ||
854 | 371 | return dateStr + "\t" + pad(message.loggerName) + " " + message.levelDesc + |
732 | 372 | "\t" + message.message + "\n"; |
373 | } |
|
374 | }; |
|
375 | ||
376 | /* |
|
377 | * Appenders |
|
378 | * These can be attached to Loggers to log to different places |
|
379 | * Simply subclass and override doAppend to implement a new one |
|
380 | */ |
|
381 | ||
77 | 382 | function Appender(formatter) { |
3 | 383 | this._name = "Appender"; |
7 | 384 | this._formatter = formatter? formatter : new BasicFormatter(); |
385 | } |
|
76 | 386 | Appender.prototype = { |
380 | 387 | QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), |
388 | ||
152 | 389 | _level: Log4Moz.Level.All, |
76 | 390 | get level() { return this._level; }, |
206 | 391 | set level(level) { this._level = level; }, |
392 | ||
1062 | 393 | append: function App_append(message) { |
2958 | 394 | if(this._level <= message.level) |
9059 | 395 | this.doAppend(this._formatter.format(message)); |
396 | }, |
|
76 | 397 | toString: function App_toString() { |
398 | return this._name + " [level=" + this._level + |
|
399 | ", formatter=" + this._formatter + "]"; |
|
400 | }, |
|
190 | 401 | doAppend: function App_doAppend(message) {} |
402 | }; |
|
403 | ||
404 | /* |
|
405 | * DumpAppender |
|
406 | * Logs to standard out |
|
407 | */ |
|
408 | ||
101 | 409 | function DumpAppender(formatter) { |
75 | 410 | this._name = "DumpAppender"; |
175 | 411 | this._formatter = formatter? formatter : new BasicFormatter(); |
412 | } |
|
76 | 413 | DumpAppender.prototype = { |
114 | 414 | __proto__: Appender.prototype, |
415 | ||
1037 | 416 | doAppend: function DApp_doAppend(message) { |
4235 | 417 | dump(message); |
418 | } |
|
419 | }; |
|
420 | ||
421 | /* |
|
422 | * ConsoleAppender |
|
423 | * Logs to the javascript console |
|
424 | */ |
|
425 | ||
78 | 426 | function ConsoleAppender(formatter) { |
6 | 427 | this._name = "ConsoleAppender"; |
8 | 428 | this._formatter = formatter; |
429 | } |
|
76 | 430 | ConsoleAppender.prototype = { |
114 | 431 | __proto__: Appender.prototype, |
432 | ||
191 | 433 | doAppend: function CApp_doAppend(message) { |
5 | 434 | if (message.level > Log4Moz.Level.Warn) { |
435 | Cu.reportError(message); |
|
436 | return; |
|
437 | } |
|
3 | 438 | Cc["@mozilla.org/consoleservice;1"]. |
8 | 439 | getService(Ci.nsIConsoleService).logStringMessage(message); |
440 | } |
|
441 | }; |
|
442 | ||
443 | /* |
|
444 | * FileAppender |
|
445 | * Logs to a file |
|
446 | */ |
|
447 | ||
76 | 448 | function FileAppender(file, formatter) { |
449 | this._name = "FileAppender"; |
|
450 | this._file = file; // nsIFile |
|
451 | this._formatter = formatter? formatter : new BasicFormatter(); |
|
452 | } |
|
76 | 453 | FileAppender.prototype = { |
114 | 454 | __proto__: Appender.prototype, |
76 | 455 | __fos: null, |
121 | 456 | get _fos() { |
135 | 457 | if (!this.__fos) |
8 | 458 | this.openStream(); |
90 | 459 | return this.__fos; |
460 | }, |
|
461 | ||
78 | 462 | openStream: function FApp_openStream() { |
4 | 463 | try { |
6 | 464 | let __fos = Cc["@mozilla.org/network/file-output-stream;1"]. |
8 | 465 | createInstance(Ci.nsIFileOutputStream); |
12 | 466 | let flags = MODE_WRONLY | MODE_CREATE | MODE_APPEND; |
16 | 467 | __fos.init(this._file, flags, PERMS_FILE, 0); |
468 | ||
8 | 469 | this.__fos = Cc["@mozilla.org/intl/converter-output-stream;1"] |
8 | 470 | .createInstance(Ci.nsIConverterOutputStream); |
12 | 471 | this.__fos.init(__fos, "UTF-8", 4096, |
14 | 472 | Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); |
473 | } catch(e) { |
|
474 | dump("Error opening stream:\n" + e); |
|
2 | 475 | } |
476 | }, |
|
477 | ||
76 | 478 | closeStream: function FApp_closeStream() { |
479 | if (!this.__fos) |
|
480 | return; |
|
481 | try { |
|
482 | this.__fos.close(); |
|
483 | this.__fos = null; |
|
484 | } catch(e) { |
|
485 | dump("Failed to close file output stream\n" + e); |
|
486 | } |
|
487 | }, |
|
488 | ||
121 | 489 | doAppend: function FApp_doAppend(message) { |
360 | 490 | if (message === null || message.length <= 0) |
491 | return; |
|
45 | 492 | try { |
315 | 493 | this._fos.writeString(message); |
494 | } catch(e) { |
|
495 | dump("Error writing file:\n" + e); |
|
45 | 496 | } |
497 | }, |
|
498 | ||
190 | 499 | clear: function FApp_clear() { |
500 | this.closeStream(); |
|
501 | try { |
|
502 | this._file.remove(false); |
|
503 | } catch (e) { |
|
504 | // XXX do something? |
|
505 | } |
|
506 | } |
|
507 | }; |
|
508 | ||
509 | /* |
|
510 | * RotatingFileAppender |
|
511 | * Similar to FileAppender, but rotates logs when they become too large |
|
512 | */ |
|
513 | ||
78 | 514 | function RotatingFileAppender(file, formatter, maxSize, maxBackups) { |
8 | 515 | if (maxSize === undefined) |
516 | maxSize = ONE_MEGABYTE * 2; |
|
517 | ||
8 | 518 | if (maxBackups === undefined) |
4 | 519 | maxBackups = 0; |
520 | ||
6 | 521 | this._name = "RotatingFileAppender"; |
6 | 522 | this._file = file; // nsIFile |
12 | 523 | this._formatter = formatter? formatter : new BasicFormatter(); |
6 | 524 | this._maxSize = maxSize; |
8 | 525 | this._maxBackups = maxBackups; |
526 | } |
|
76 | 527 | RotatingFileAppender.prototype = { |
114 | 528 | __proto__: FileAppender.prototype, |
529 | ||
121 | 530 | doAppend: function RFApp_doAppend(message) { |
360 | 531 | if (message === null || message.length <= 0) |
532 | return; |
|
45 | 533 | try { |
180 | 534 | this.rotateLogs(); |
405 | 535 | FileAppender.prototype.doAppend.call(this, message); |
536 | } catch(e) { |
|
537 | dump("Error writing file:" + e + "\n"); |
|
45 | 538 | } |
539 | }, |
|
540 | ||
235 | 541 | rotateLogs: function RFApp_rotateLogs() { |
225 | 542 | if(this._file.exists() && |
225 | 543 | this._file.fileSize < this._maxSize) |
90 | 544 | return; |
545 | ||
546 | this.closeStream(); |
|
547 | ||
548 | for (let i = this.maxBackups - 1; i > 0; i--){ |
|
549 | let backup = this._file.parent.clone(); |
|
550 | backup.append(this._file.leafName + "." + i); |
|
551 | if (backup.exists()) |
|
552 | backup.moveTo(this._file.parent, this._file.leafName + "." + (i + 1)); |
|
553 | } |
|
554 | ||
555 | let cur = this._file.clone(); |
|
556 | if (cur.exists()) |
|
557 | cur.moveTo(cur.parent, cur.leafName + ".1"); |
|
558 | ||
559 | // Note: this._file still points to the same file |
|
560 | } |
|
38 | 561 | }; |