log4moz.js

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
};