UNPKG

49.5 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2015 Trent Mick.
3 * Copyright (c) 2015 Joyent Inc.
4 *
5 * The bunyan logging library for node.js.
6 *
7 * -*- mode: js -*-
8 * vim: expandtab:ts=4:sw=4
9 */
10
11var VERSION = '1.8.8';
12
13/*
14 * Bunyan log format version. This becomes the 'v' field on all log records.
15 * This will be incremented if there is any backward incompatible change to
16 * the log record format. Details will be in 'CHANGES.md' (the change log).
17 */
18var LOG_VERSION = 0;
19
20
21var xxx = function xxx(s) { // internal dev/debug logging
22 var args = ['XX' + 'X: '+s].concat(
23 Array.prototype.slice.call(arguments, 1));
24 console.error.apply(this, args);
25};
26var xxx = function xxx() {}; // comment out to turn on debug logging
27
28
29/*
30 * Runtime environment notes:
31 *
32 * Bunyan is intended to run in a number of runtime environments. Here are
33 * some notes on differences for those envs and how the code copes.
34 *
35 * - node.js: The primary target environment.
36 * - NW.js: http://nwjs.io/ An *app* environment that feels like both a
37 * node env -- it has node-like globals (`process`, `global`) and
38 * browser-like globals (`window`, `navigator`). My *understanding* is that
39 * bunyan can operate as if this is vanilla node.js.
40 * - browser: Failing the above, we sniff using the `window` global
41 * <https://developer.mozilla.org/en-US/docs/Web/API/Window/window>.
42 * - browserify: http://browserify.org/ A browser-targetting bundler of
43 * node.js deps. The runtime is a browser env, so can't use fs access,
44 * etc. Browserify's build looks for `require(<single-string>)` imports
45 * to bundle. For some imports it won't be able to handle, we "hide"
46 * from browserify with `require('frobshizzle' + '')`.
47 * - Other? Please open issues if things are broken.
48 */
49var runtimeEnv;
50if (typeof (process) !== 'undefined' && process.versions) {
51 if (process.versions.nw) {
52 runtimeEnv = 'nw';
53 } else if (process.versions.node) {
54 runtimeEnv = 'node';
55 }
56}
57if (!runtimeEnv && typeof (window) !== 'undefined' &&
58 window.window === window) {
59 runtimeEnv = 'browser';
60}
61if (!runtimeEnv) {
62 throw new Error('unknown runtime environment');
63}
64
65
66var os, fs, dtrace;
67if (runtimeEnv === 'browser') {
68 os = {
69 hostname: function () {
70 return window.location.host;
71 }
72 };
73 fs = {};
74 dtrace = null;
75} else {
76 os = require('os');
77 fs = require('fs');
78 try {
79 dtrace = require('dtrace-provider' + '');
80 } catch (e) {
81 dtrace = null;
82 }
83}
84var util = require('util');
85var assert = require('assert');
86var EventEmitter = require('events').EventEmitter;
87var stream = require('stream');
88
89try {
90 var safeJsonStringify = require('safe-json-stringify');
91} catch (e) {
92 safeJsonStringify = null;
93}
94if (process.env.BUNYAN_TEST_NO_SAFE_JSON_STRINGIFY) {
95 safeJsonStringify = null;
96}
97
98// The 'mv' module is required for rotating-file stream support.
99try {
100 var mv = require('mv' + '');
101} catch (e) {
102 mv = null;
103}
104
105try {
106 var sourceMapSupport = require('source-map-support' + '');
107} catch (_) {
108 sourceMapSupport = null;
109}
110
111
112//---- Internal support stuff
113
114/**
115 * A shallow copy of an object. Bunyan logging attempts to never cause
116 * exceptions, so this function attempts to handle non-objects gracefully.
117 */
118function objCopy(obj) {
119 if (obj == null) { // null or undefined
120 return obj;
121 } else if (Array.isArray(obj)) {
122 return obj.slice();
123 } else if (typeof (obj) === 'object') {
124 var copy = {};
125 Object.keys(obj).forEach(function (k) {
126 copy[k] = obj[k];
127 });
128 return copy;
129 } else {
130 return obj;
131 }
132}
133
134var format = util.format;
135if (!format) {
136 // If node < 0.6, then use its `util.format`:
137 // <https://github.com/joyent/node/blob/master/lib/util.js#L22>:
138 var inspect = util.inspect;
139 var formatRegExp = /%[sdj%]/g;
140 format = function format(f) {
141 if (typeof (f) !== 'string') {
142 var objects = [];
143 for (var i = 0; i < arguments.length; i++) {
144 objects.push(inspect(arguments[i]));
145 }
146 return objects.join(' ');
147 }
148
149 var i = 1;
150 var args = arguments;
151 var len = args.length;
152 var str = String(f).replace(formatRegExp, function (x) {
153 if (i >= len)
154 return x;
155 switch (x) {
156 case '%s': return String(args[i++]);
157 case '%d': return Number(args[i++]);
158 case '%j': return fastAndSafeJsonStringify(args[i++]);
159 case '%%': return '%';
160 default:
161 return x;
162 }
163 });
164 for (var x = args[i]; i < len; x = args[++i]) {
165 if (x === null || typeof (x) !== 'object') {
166 str += ' ' + x;
167 } else {
168 str += ' ' + inspect(x);
169 }
170 }
171 return str;
172 };
173}
174
175
176/**
177 * Gather some caller info 3 stack levels up.
178 * See <http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi>.
179 */
180function getCaller3Info() {
181 if (this === undefined) {
182 // Cannot access caller info in 'strict' mode.
183 return;
184 }
185 var obj = {};
186 var saveLimit = Error.stackTraceLimit;
187 var savePrepare = Error.prepareStackTrace;
188 Error.stackTraceLimit = 3;
189
190 Error.prepareStackTrace = function (_, stack) {
191 var caller = stack[2];
192 if (sourceMapSupport) {
193 caller = sourceMapSupport.wrapCallSite(caller);
194 }
195 obj.file = caller.getFileName();
196 obj.line = caller.getLineNumber();
197 var func = caller.getFunctionName();
198 if (func)
199 obj.func = func;
200 };
201 Error.captureStackTrace(this, getCaller3Info);
202 this.stack;
203
204 Error.stackTraceLimit = saveLimit;
205 Error.prepareStackTrace = savePrepare;
206 return obj;
207}
208
209
210function _indent(s, indent) {
211 if (!indent) indent = ' ';
212 var lines = s.split(/\r?\n/g);
213 return indent + lines.join('\n' + indent);
214}
215
216
217/**
218 * Warn about an bunyan processing error.
219 *
220 * @param msg {String} Message with which to warn.
221 * @param dedupKey {String} Optional. A short string key for this warning to
222 * have its warning only printed once.
223 */
224function _warn(msg, dedupKey) {
225 assert.ok(msg);
226 if (dedupKey) {
227 if (_warned[dedupKey]) {
228 return;
229 }
230 _warned[dedupKey] = true;
231 }
232 process.stderr.write(msg + '\n');
233}
234function _haveWarned(dedupKey) {
235 return _warned[dedupKey];
236}
237var _warned = {};
238
239
240function ConsoleRawStream() {}
241ConsoleRawStream.prototype.write = function (rec) {
242 if (rec.level < INFO) {
243 console.log(rec);
244 } else if (rec.level < WARN) {
245 console.info(rec);
246 } else if (rec.level < ERROR) {
247 console.warn(rec);
248 } else {
249 console.error(rec);
250 }
251};
252
253
254//---- Levels
255
256var TRACE = 10;
257var DEBUG = 20;
258var INFO = 30;
259var WARN = 40;
260var ERROR = 50;
261var FATAL = 60;
262
263var levelFromName = {
264 'trace': TRACE,
265 'debug': DEBUG,
266 'info': INFO,
267 'warn': WARN,
268 'error': ERROR,
269 'fatal': FATAL
270};
271var nameFromLevel = {};
272Object.keys(levelFromName).forEach(function (name) {
273 nameFromLevel[levelFromName[name]] = name;
274});
275
276// Dtrace probes.
277var dtp = undefined;
278var probes = dtrace && {};
279
280/**
281 * Resolve a level number, name (upper or lowercase) to a level number value.
282 *
283 * @param nameOrNum {String|Number} A level name (case-insensitive) or positive
284 * integer level.
285 * @api public
286 */
287function resolveLevel(nameOrNum) {
288 var level;
289 var type = typeof (nameOrNum);
290 if (type === 'string') {
291 level = levelFromName[nameOrNum.toLowerCase()];
292 if (!level) {
293 throw new Error(format('unknown level name: "%s"', nameOrNum));
294 }
295 } else if (type !== 'number') {
296 throw new TypeError(format('cannot resolve level: invalid arg (%s):',
297 type, nameOrNum));
298 } else if (nameOrNum < 0 || Math.floor(nameOrNum) !== nameOrNum) {
299 throw new TypeError(format('level is not a positive integer: %s',
300 nameOrNum));
301 } else {
302 level = nameOrNum;
303 }
304 return level;
305}
306
307
308function isWritable(obj) {
309 if (obj instanceof stream.Writable) {
310 return true;
311 }
312 return typeof (obj.write) === 'function';
313}
314
315
316//---- Logger class
317
318/**
319 * Create a Logger instance.
320 *
321 * @param options {Object} See documentation for full details. At minimum
322 * this must include a 'name' string key. Configuration keys:
323 * - `streams`: specify the logger output streams. This is an array of
324 * objects with these fields:
325 * - `type`: The stream type. See README.md for full details.
326 * Often this is implied by the other fields. Examples are
327 * 'file', 'stream' and "raw".
328 * - `level`: Defaults to 'info'.
329 * - `path` or `stream`: The specify the file path or writeable
330 * stream to which log records are written. E.g.
331 * `stream: process.stdout`.
332 * - `closeOnExit` (boolean): Optional. Default is true for a
333 * 'file' stream when `path` is given, false otherwise.
334 * See README.md for full details.
335 * - `level`: set the level for a single output stream (cannot be used
336 * with `streams`)
337 * - `stream`: the output stream for a logger with just one, e.g.
338 * `process.stdout` (cannot be used with `streams`)
339 * - `serializers`: object mapping log record field names to
340 * serializing functions. See README.md for details.
341 * - `src`: Boolean (default false). Set true to enable 'src' automatic
342 * field with log call source info.
343 * All other keys are log record fields.
344 *
345 * An alternative *internal* call signature is used for creating a child:
346 * new Logger(<parent logger>, <child options>[, <child opts are simple>]);
347 *
348 * @param _childSimple (Boolean) An assertion that the given `_childOptions`
349 * (a) only add fields (no config) and (b) no serialization handling is
350 * required for them. IOW, this is a fast path for frequent child
351 * creation.
352 */
353function Logger(options, _childOptions, _childSimple) {
354 xxx('Logger start:', options)
355 if (!(this instanceof Logger)) {
356 return new Logger(options, _childOptions);
357 }
358
359 // Input arg validation.
360 var parent;
361 if (_childOptions !== undefined) {
362 parent = options;
363 options = _childOptions;
364 if (!(parent instanceof Logger)) {
365 throw new TypeError(
366 'invalid Logger creation: do not pass a second arg');
367 }
368 }
369 if (!options) {
370 throw new TypeError('options (object) is required');
371 }
372 if (!parent) {
373 if (!options.name) {
374 throw new TypeError('options.name (string) is required');
375 }
376 } else {
377 if (options.name) {
378 throw new TypeError(
379 'invalid options.name: child cannot set logger name');
380 }
381 }
382 if (options.stream && options.streams) {
383 throw new TypeError('cannot mix "streams" and "stream" options');
384 }
385 if (options.streams && !Array.isArray(options.streams)) {
386 throw new TypeError('invalid options.streams: must be an array')
387 }
388 if (options.serializers && (typeof (options.serializers) !== 'object' ||
389 Array.isArray(options.serializers))) {
390 throw new TypeError('invalid options.serializers: must be an object')
391 }
392
393 EventEmitter.call(this);
394
395 // Fast path for simple child creation.
396 if (parent && _childSimple) {
397 // `_isSimpleChild` is a signal to stream close handling that this child
398 // owns none of its streams.
399 this._isSimpleChild = true;
400
401 this._level = parent._level;
402 this.streams = parent.streams;
403 this.serializers = parent.serializers;
404 this.src = parent.src;
405 var fields = this.fields = {};
406 var parentFieldNames = Object.keys(parent.fields);
407 for (var i = 0; i < parentFieldNames.length; i++) {
408 var name = parentFieldNames[i];
409 fields[name] = parent.fields[name];
410 }
411 var names = Object.keys(options);
412 for (var i = 0; i < names.length; i++) {
413 var name = names[i];
414 fields[name] = options[name];
415 }
416 return;
417 }
418
419 // Start values.
420 var self = this;
421 if (parent) {
422 this._level = parent._level;
423 this.streams = [];
424 for (var i = 0; i < parent.streams.length; i++) {
425 var s = objCopy(parent.streams[i]);
426 s.closeOnExit = false; // Don't own parent stream.
427 this.streams.push(s);
428 }
429 this.serializers = objCopy(parent.serializers);
430 this.src = parent.src;
431 this.fields = objCopy(parent.fields);
432 if (options.level) {
433 this.level(options.level);
434 }
435 } else {
436 this._level = Number.POSITIVE_INFINITY;
437 this.streams = [];
438 this.serializers = null;
439 this.src = false;
440 this.fields = {};
441 }
442
443 if (!dtp && dtrace) {
444 dtp = dtrace.createDTraceProvider('bunyan');
445
446 for (var level in levelFromName) {
447 var probe;
448
449 probes[levelFromName[level]] = probe =
450 dtp.addProbe('log-' + level, 'char *');
451
452 // Explicitly add a reference to dtp to prevent it from being GC'd
453 probe.dtp = dtp;
454 }
455
456 dtp.enable();
457 }
458
459 // Handle *config* options (i.e. options that are not just plain data
460 // for log records).
461 if (options.stream) {
462 self.addStream({
463 type: 'stream',
464 stream: options.stream,
465 closeOnExit: false,
466 level: options.level
467 });
468 } else if (options.streams) {
469 options.streams.forEach(function (s) {
470 self.addStream(s, options.level);
471 });
472 } else if (parent && options.level) {
473 this.level(options.level);
474 } else if (!parent) {
475 if (runtimeEnv === 'browser') {
476 /*
477 * In the browser we'll be emitting to console.log by default.
478 * Any console.log worth its salt these days can nicely render
479 * and introspect objects (e.g. the Firefox and Chrome console)
480 * so let's emit the raw log record. Are there browsers for which
481 * that breaks things?
482 */
483 self.addStream({
484 type: 'raw',
485 stream: new ConsoleRawStream(),
486 closeOnExit: false,
487 level: options.level
488 });
489 } else {
490 self.addStream({
491 type: 'stream',
492 stream: process.stdout,
493 closeOnExit: false,
494 level: options.level
495 });
496 }
497 }
498 if (options.serializers) {
499 self.addSerializers(options.serializers);
500 }
501 if (options.src) {
502 this.src = true;
503 }
504 xxx('Logger: ', self)
505
506 // Fields.
507 // These are the default fields for log records (minus the attributes
508 // removed in this constructor). To allow storing raw log records
509 // (unrendered), `this.fields` must never be mutated. Create a copy for
510 // any changes.
511 var fields = objCopy(options);
512 delete fields.stream;
513 delete fields.level;
514 delete fields.streams;
515 delete fields.serializers;
516 delete fields.src;
517 if (this.serializers) {
518 this._applySerializers(fields);
519 }
520 if (!fields.hostname && !self.fields.hostname) {
521 fields.hostname = os.hostname();
522 }
523 if (!fields.pid) {
524 fields.pid = process.pid;
525 }
526 Object.keys(fields).forEach(function (k) {
527 self.fields[k] = fields[k];
528 });
529}
530
531util.inherits(Logger, EventEmitter);
532
533
534/**
535 * Add a stream
536 *
537 * @param stream {Object}. Object with these fields:
538 * - `type`: The stream type. See README.md for full details.
539 * Often this is implied by the other fields. Examples are
540 * 'file', 'stream' and "raw".
541 * - `path` or `stream`: The specify the file path or writeable
542 * stream to which log records are written. E.g.
543 * `stream: process.stdout`.
544 * - `level`: Optional. Falls back to `defaultLevel`.
545 * - `closeOnExit` (boolean): Optional. Default is true for a
546 * 'file' stream when `path` is given, false otherwise.
547 * See README.md for full details.
548 * @param defaultLevel {Number|String} Optional. A level to use if
549 * `stream.level` is not set. If neither is given, this defaults to INFO.
550 */
551Logger.prototype.addStream = function addStream(s, defaultLevel) {
552 var self = this;
553 if (defaultLevel === null || defaultLevel === undefined) {
554 defaultLevel = INFO;
555 }
556
557 s = objCopy(s);
558
559 // Implicit 'type' from other args.
560 if (!s.type) {
561 if (s.stream) {
562 s.type = 'stream';
563 } else if (s.path) {
564 s.type = 'file'
565 }
566 }
567 s.raw = (s.type === 'raw'); // PERF: Allow for faster check in `_emit`.
568
569 if (s.level !== undefined) {
570 s.level = resolveLevel(s.level);
571 } else {
572 s.level = resolveLevel(defaultLevel);
573 }
574 if (s.level < self._level) {
575 self._level = s.level;
576 }
577
578 switch (s.type) {
579 case 'stream':
580 assert.ok(isWritable(s.stream),
581 '"stream" stream is not writable: ' + util.inspect(s.stream));
582
583 if (!s.closeOnExit) {
584 s.closeOnExit = false;
585 }
586 break;
587 case 'file':
588 if (s.reemitErrorEvents === undefined) {
589 s.reemitErrorEvents = true;
590 }
591 if (!s.stream) {
592 s.stream = fs.createWriteStream(s.path,
593 {flags: 'a', encoding: 'utf8'});
594 if (!s.closeOnExit) {
595 s.closeOnExit = true;
596 }
597 } else {
598 if (!s.closeOnExit) {
599 s.closeOnExit = false;
600 }
601 }
602 break;
603 case 'rotating-file':
604 assert.ok(!s.stream,
605 '"rotating-file" stream should not give a "stream"');
606 assert.ok(s.path);
607 assert.ok(mv, '"rotating-file" stream type is not supported: '
608 + 'missing "mv" module');
609 s.stream = new RotatingFileStream(s);
610 if (!s.closeOnExit) {
611 s.closeOnExit = true;
612 }
613 break;
614 case 'raw':
615 if (!s.closeOnExit) {
616 s.closeOnExit = false;
617 }
618 break;
619 default:
620 throw new TypeError('unknown stream type "' + s.type + '"');
621 }
622
623 if (s.reemitErrorEvents && typeof (s.stream.on) === 'function') {
624 // TODO: When we have `<logger>.close()`, it should remove event
625 // listeners to not leak Logger instances.
626 s.stream.on('error', function onStreamError(err) {
627 self.emit('error', err, s);
628 });
629 }
630
631 self.streams.push(s);
632 delete self.haveNonRawStreams; // reset
633}
634
635
636/**
637 * Add serializers
638 *
639 * @param serializers {Object} Optional. Object mapping log record field names
640 * to serializing functions. See README.md for details.
641 */
642Logger.prototype.addSerializers = function addSerializers(serializers) {
643 var self = this;
644
645 if (!self.serializers) {
646 self.serializers = {};
647 }
648 Object.keys(serializers).forEach(function (field) {
649 var serializer = serializers[field];
650 if (typeof (serializer) !== 'function') {
651 throw new TypeError(format(
652 'invalid serializer for "%s" field: must be a function',
653 field));
654 } else {
655 self.serializers[field] = serializer;
656 }
657 });
658}
659
660
661
662/**
663 * Create a child logger, typically to add a few log record fields.
664 *
665 * This can be useful when passing a logger to a sub-component, e.g. a
666 * 'wuzzle' component of your service:
667 *
668 * var wuzzleLog = log.child({component: 'wuzzle'})
669 * var wuzzle = new Wuzzle({..., log: wuzzleLog})
670 *
671 * Then log records from the wuzzle code will have the same structure as
672 * the app log, *plus the component='wuzzle' field*.
673 *
674 * @param options {Object} Optional. Set of options to apply to the child.
675 * All of the same options for a new Logger apply here. Notes:
676 * - The parent's streams are inherited and cannot be removed in this
677 * call. Any given `streams` are *added* to the set inherited from
678 * the parent.
679 * - The parent's serializers are inherited, though can effectively be
680 * overwritten by using duplicate keys.
681 * - Can use `level` to set the level of the streams inherited from
682 * the parent. The level for the parent is NOT affected.
683 * @param simple {Boolean} Optional. Set to true to assert that `options`
684 * (a) only add fields (no config) and (b) no serialization handling is
685 * required for them. IOW, this is a fast path for frequent child
686 * creation. See 'tools/timechild.js' for numbers.
687 */
688Logger.prototype.child = function (options, simple) {
689 return new (this.constructor)(this, options || {}, simple);
690}
691
692
693/**
694 * A convenience method to reopen 'file' streams on a logger. This can be
695 * useful with external log rotation utilities that move and re-open log files
696 * (e.g. logrotate on Linux, logadm on SmartOS/Illumos). Those utilities
697 * typically have rotation options to copy-and-truncate the log file, but
698 * you may not want to use that. An alternative is to do this in your
699 * application:
700 *
701 * var log = bunyan.createLogger(...);
702 * ...
703 * process.on('SIGUSR2', function () {
704 * log.reopenFileStreams();
705 * });
706 * ...
707 *
708 * See <https://github.com/trentm/node-bunyan/issues/104>.
709 */
710Logger.prototype.reopenFileStreams = function () {
711 var self = this;
712 self.streams.forEach(function (s) {
713 if (s.type === 'file') {
714 if (s.stream) {
715 // Not sure if typically would want this, or more immediate
716 // `s.stream.destroy()`.
717 s.stream.end();
718 s.stream.destroySoon();
719 delete s.stream;
720 }
721 s.stream = fs.createWriteStream(s.path,
722 {flags: 'a', encoding: 'utf8'});
723 s.stream.on('error', function (err) {
724 self.emit('error', err, s);
725 });
726 }
727 });
728};
729
730
731/* BEGIN JSSTYLED */
732/**
733 * Close this logger.
734 *
735 * This closes streams (that it owns, as per 'endOnClose' attributes on
736 * streams), etc. Typically you **don't** need to bother calling this.
737Logger.prototype.close = function () {
738 if (this._closed) {
739 return;
740 }
741 if (!this._isSimpleChild) {
742 self.streams.forEach(function (s) {
743 if (s.endOnClose) {
744 xxx('closing stream s:', s);
745 s.stream.end();
746 s.endOnClose = false;
747 }
748 });
749 }
750 this._closed = true;
751}
752 */
753/* END JSSTYLED */
754
755
756/**
757 * Get/set the level of all streams on this logger.
758 *
759 * Get Usage:
760 * // Returns the current log level (lowest level of all its streams).
761 * log.level() -> INFO
762 *
763 * Set Usage:
764 * log.level(INFO) // set all streams to level INFO
765 * log.level('info') // can use 'info' et al aliases
766 */
767Logger.prototype.level = function level(value) {
768 if (value === undefined) {
769 return this._level;
770 }
771 var newLevel = resolveLevel(value);
772 var len = this.streams.length;
773 for (var i = 0; i < len; i++) {
774 this.streams[i].level = newLevel;
775 }
776 this._level = newLevel;
777}
778
779
780/**
781 * Get/set the level of a particular stream on this logger.
782 *
783 * Get Usage:
784 * // Returns an array of the levels of each stream.
785 * log.levels() -> [TRACE, INFO]
786 *
787 * // Returns a level of the identified stream.
788 * log.levels(0) -> TRACE // level of stream at index 0
789 * log.levels('foo') // level of stream with name 'foo'
790 *
791 * Set Usage:
792 * log.levels(0, INFO) // set level of stream 0 to INFO
793 * log.levels(0, 'info') // can use 'info' et al aliases
794 * log.levels('foo', WARN) // set stream named 'foo' to WARN
795 *
796 * Stream names: When streams are defined, they can optionally be given
797 * a name. For example,
798 * log = new Logger({
799 * streams: [
800 * {
801 * name: 'foo',
802 * path: '/var/log/my-service/foo.log'
803 * level: 'trace'
804 * },
805 * ...
806 *
807 * @param name {String|Number} The stream index or name.
808 * @param value {Number|String} The level value (INFO) or alias ('info').
809 * If not given, this is a 'get' operation.
810 * @throws {Error} If there is no stream with the given name.
811 */
812Logger.prototype.levels = function levels(name, value) {
813 if (name === undefined) {
814 assert.equal(value, undefined);
815 return this.streams.map(
816 function (s) { return s.level });
817 }
818 var stream;
819 if (typeof (name) === 'number') {
820 stream = this.streams[name];
821 if (stream === undefined) {
822 throw new Error('invalid stream index: ' + name);
823 }
824 } else {
825 var len = this.streams.length;
826 for (var i = 0; i < len; i++) {
827 var s = this.streams[i];
828 if (s.name === name) {
829 stream = s;
830 break;
831 }
832 }
833 if (!stream) {
834 throw new Error(format('no stream with name "%s"', name));
835 }
836 }
837 if (value === undefined) {
838 return stream.level;
839 } else {
840 var newLevel = resolveLevel(value);
841 stream.level = newLevel;
842 if (newLevel < this._level) {
843 this._level = newLevel;
844 }
845 }
846}
847
848
849/**
850 * Apply registered serializers to the appropriate keys in the given fields.
851 *
852 * Pre-condition: This is only called if there is at least one serializer.
853 *
854 * @param fields (Object) The log record fields.
855 * @param excludeFields (Object) Optional mapping of keys to `true` for
856 * keys to NOT apply a serializer.
857 */
858Logger.prototype._applySerializers = function (fields, excludeFields) {
859 var self = this;
860
861 xxx('_applySerializers: excludeFields', excludeFields);
862
863 // Check each serializer against these (presuming number of serializers
864 // is typically less than number of fields).
865 Object.keys(this.serializers).forEach(function (name) {
866 if (fields[name] === undefined ||
867 (excludeFields && excludeFields[name]))
868 {
869 return;
870 }
871 xxx('_applySerializers; apply to "%s" key', name)
872 try {
873 fields[name] = self.serializers[name](fields[name]);
874 } catch (err) {
875 _warn(format('bunyan: ERROR: Exception thrown from the "%s" '
876 + 'Bunyan serializer. This should never happen. This is a bug'
877 + 'in that serializer function.\n%s',
878 name, err.stack || err));
879 fields[name] = format('(Error in Bunyan log "%s" serializer '
880 + 'broke field. See stderr for details.)', name);
881 }
882 });
883}
884
885
886/**
887 * Emit a log record.
888 *
889 * @param rec {log record}
890 * @param noemit {Boolean} Optional. Set to true to skip emission
891 * and just return the JSON string.
892 */
893Logger.prototype._emit = function (rec, noemit) {
894 var i;
895
896 // Lazily determine if this Logger has non-'raw' streams. If there are
897 // any, then we need to stringify the log record.
898 if (this.haveNonRawStreams === undefined) {
899 this.haveNonRawStreams = false;
900 for (i = 0; i < this.streams.length; i++) {
901 if (!this.streams[i].raw) {
902 this.haveNonRawStreams = true;
903 break;
904 }
905 }
906 }
907
908 // Stringify the object (creates a warning str on error).
909 var str;
910 if (noemit || this.haveNonRawStreams) {
911 str = fastAndSafeJsonStringify(rec) + '\n';
912 }
913
914 if (noemit)
915 return str;
916
917 var level = rec.level;
918 for (i = 0; i < this.streams.length; i++) {
919 var s = this.streams[i];
920 if (s.level <= level) {
921 xxx('writing log rec "%s" to "%s" stream (%d <= %d): %j',
922 rec.msg, s.type, s.level, level, rec);
923 s.stream.write(s.raw ? rec : str);
924 }
925 };
926
927 return str;
928}
929
930
931/**
932 * Build a record object suitable for emitting from the arguments
933 * provided to the a log emitter.
934 */
935function mkRecord(log, minLevel, args) {
936 var excludeFields, fields, msgArgs;
937 if (args[0] instanceof Error) {
938 // `log.<level>(err, ...)`
939 fields = {
940 // Use this Logger's err serializer, if defined.
941 err: (log.serializers && log.serializers.err
942 ? log.serializers.err(args[0])
943 : Logger.stdSerializers.err(args[0]))
944 };
945 excludeFields = {err: true};
946 if (args.length === 1) {
947 msgArgs = [fields.err.message];
948 } else {
949 msgArgs = args.slice(1);
950 }
951 } else if (typeof (args[0]) !== 'object' || Array.isArray(args[0])) {
952 // `log.<level>(msg, ...)`
953 fields = null;
954 msgArgs = args.slice();
955 } else if (Buffer.isBuffer(args[0])) { // `log.<level>(buf, ...)`
956 // Almost certainly an error, show `inspect(buf)`. See bunyan
957 // issue #35.
958 fields = null;
959 msgArgs = args.slice();
960 msgArgs[0] = util.inspect(msgArgs[0]);
961 } else { // `log.<level>(fields, msg, ...)`
962 fields = args[0];
963 if (fields && args.length === 1 && fields.err &&
964 fields.err instanceof Error)
965 {
966 msgArgs = [fields.err.message];
967 } else {
968 msgArgs = args.slice(1);
969 }
970 }
971
972 // Build up the record object.
973 var rec = objCopy(log.fields);
974 var level = rec.level = minLevel;
975 var recFields = (fields ? objCopy(fields) : null);
976 if (recFields) {
977 if (log.serializers) {
978 log._applySerializers(recFields, excludeFields);
979 }
980 Object.keys(recFields).forEach(function (k) {
981 rec[k] = recFields[k];
982 });
983 }
984 rec.msg = format.apply(log, msgArgs);
985 if (!rec.time) {
986 rec.time = (new Date());
987 }
988 // Get call source info
989 if (log.src && !rec.src) {
990 rec.src = getCaller3Info()
991 }
992 rec.v = LOG_VERSION;
993
994 return rec;
995};
996
997
998/**
999 * Build an array that dtrace-provider can use to fire a USDT probe. If we've
1000 * already built the appropriate string, we use it. Otherwise, build the
1001 * record object and stringify it.
1002 */
1003function mkProbeArgs(str, log, minLevel, msgArgs) {
1004 return [ str || log._emit(mkRecord(log, minLevel, msgArgs), true) ];
1005}
1006
1007
1008/**
1009 * Build a log emitter function for level minLevel. I.e. this is the
1010 * creator of `log.info`, `log.error`, etc.
1011 */
1012function mkLogEmitter(minLevel) {
1013 return function () {
1014 var log = this;
1015 var str = null;
1016 var rec = null;
1017
1018 if (!this._emit) {
1019 /*
1020 * Show this invalid Bunyan usage warning *once*.
1021 *
1022 * See <https://github.com/trentm/node-bunyan/issues/100> for
1023 * an example of how this can happen.
1024 */
1025 var dedupKey = 'unbound';
1026 if (!_haveWarned[dedupKey]) {
1027 var caller = getCaller3Info();
1028 _warn(format('bunyan usage error: %s:%s: attempt to log '
1029 + 'with an unbound log method: `this` is: %s',
1030 caller.file, caller.line, util.inspect(this)),
1031 dedupKey);
1032 }
1033 return;
1034 } else if (arguments.length === 0) { // `log.<level>()`
1035 return (this._level <= minLevel);
1036 }
1037
1038 var msgArgs = new Array(arguments.length);
1039 for (var i = 0; i < msgArgs.length; ++i) {
1040 msgArgs[i] = arguments[i];
1041 }
1042
1043 if (this._level <= minLevel) {
1044 rec = mkRecord(log, minLevel, msgArgs);
1045 str = this._emit(rec);
1046 }
1047
1048 if (probes) {
1049 probes[minLevel].fire(mkProbeArgs, str, log, minLevel, msgArgs);
1050 }
1051 }
1052}
1053
1054
1055/**
1056 * The functions below log a record at a specific level.
1057 *
1058 * Usages:
1059 * log.<level>() -> boolean is-trace-enabled
1060 * log.<level>(<Error> err, [<string> msg, ...])
1061 * log.<level>(<string> msg, ...)
1062 * log.<level>(<object> fields, <string> msg, ...)
1063 *
1064 * where <level> is the lowercase version of the log level. E.g.:
1065 *
1066 * log.info()
1067 *
1068 * @params fields {Object} Optional set of additional fields to log.
1069 * @params msg {String} Log message. This can be followed by additional
1070 * arguments that are handled like
1071 * [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
1072 */
1073Logger.prototype.trace = mkLogEmitter(TRACE);
1074Logger.prototype.debug = mkLogEmitter(DEBUG);
1075Logger.prototype.info = mkLogEmitter(INFO);
1076Logger.prototype.warn = mkLogEmitter(WARN);
1077Logger.prototype.error = mkLogEmitter(ERROR);
1078Logger.prototype.fatal = mkLogEmitter(FATAL);
1079
1080
1081
1082//---- Standard serializers
1083// A serializer is a function that serializes a JavaScript object to a
1084// JSON representation for logging. There is a standard set of presumed
1085// interesting objects in node.js-land.
1086
1087Logger.stdSerializers = {};
1088
1089// Serialize an HTTP request.
1090Logger.stdSerializers.req = function req(req) {
1091 if (!req || !req.connection)
1092 return req;
1093 return {
1094 method: req.method,
1095 url: req.url,
1096 headers: req.headers,
1097 remoteAddress: req.connection.remoteAddress,
1098 remotePort: req.connection.remotePort
1099 };
1100 // Trailers: Skipping for speed. If you need trailers in your app, then
1101 // make a custom serializer.
1102 //if (Object.keys(trailers).length > 0) {
1103 // obj.trailers = req.trailers;
1104 //}
1105};
1106
1107// Serialize an HTTP response.
1108Logger.stdSerializers.res = function res(res) {
1109 if (!res || !res.statusCode)
1110 return res;
1111 return {
1112 statusCode: res.statusCode,
1113 header: res._header
1114 }
1115};
1116
1117
1118/*
1119 * This function dumps long stack traces for exceptions having a cause()
1120 * method. The error classes from
1121 * [verror](https://github.com/davepacheco/node-verror) and
1122 * [restify v2.0](https://github.com/mcavage/node-restify) are examples.
1123 *
1124 * Based on `dumpException` in
1125 * https://github.com/davepacheco/node-extsprintf/blob/master/lib/extsprintf.js
1126 */
1127function getFullErrorStack(ex)
1128{
1129 var ret = ex.stack || ex.toString();
1130 if (ex.cause && typeof (ex.cause) === 'function') {
1131 var cex = ex.cause();
1132 if (cex) {
1133 ret += '\nCaused by: ' + getFullErrorStack(cex);
1134 }
1135 }
1136 return (ret);
1137}
1138
1139// Serialize an Error object
1140// (Core error properties are enumerable in node 0.4, not in 0.6).
1141var errSerializer = Logger.stdSerializers.err = function err(err) {
1142 if (!err || !err.stack)
1143 return err;
1144 var obj = {
1145 message: err.message,
1146 name: err.name,
1147 stack: getFullErrorStack(err),
1148 code: err.code,
1149 signal: err.signal
1150 }
1151 return obj;
1152};
1153
1154
1155// A JSON stringifier that handles cycles safely - tracks seen values in a Set.
1156function safeCyclesSet() {
1157 var seen = new Set();
1158 return function (key, val) {
1159 if (!val || typeof (val) !== 'object') {
1160 return val;
1161 }
1162 if (seen.has(val)) {
1163 return '[Circular]';
1164 }
1165 seen.add(val);
1166 return val;
1167 };
1168}
1169
1170/**
1171 * A JSON stringifier that handles cycles safely - tracks seen vals in an Array.
1172 *
1173 * Note: This approach has performance problems when dealing with large objects,
1174 * see trentm/node-bunyan#445, but since this is the only option for node 0.10
1175 * and earlier (as Set was introduced in Node 0.12), it's used as a fallback
1176 * when Set is not available.
1177 */
1178function safeCyclesArray() {
1179 var seen = [];
1180 return function (key, val) {
1181 if (!val || typeof (val) !== 'object') {
1182 return val;
1183 }
1184 if (seen.indexOf(val) !== -1) {
1185 return '[Circular]';
1186 }
1187 seen.push(val);
1188 return val;
1189 };
1190}
1191
1192/**
1193 * A JSON stringifier that handles cycles safely.
1194 *
1195 * Usage: JSON.stringify(obj, safeCycles())
1196 *
1197 * Choose the best safe cycle function from what is available - see
1198 * trentm/node-bunyan#445.
1199 */
1200var safeCycles = typeof (Set) !== 'undefined' ? safeCyclesSet : safeCyclesArray;
1201
1202/**
1203 * A fast JSON.stringify that handles cycles and getter exceptions (when
1204 * safeJsonStringify is installed).
1205 *
1206 * This function attempts to use the regular JSON.stringify for speed, but on
1207 * error (e.g. JSON cycle detection exception) it falls back to safe stringify
1208 * handlers that can deal with cycles and/or getter exceptions.
1209 */
1210function fastAndSafeJsonStringify(rec) {
1211 try {
1212 return JSON.stringify(rec);
1213 } catch (ex) {
1214 try {
1215 return JSON.stringify(rec, safeCycles());
1216 } catch (e) {
1217 if (safeJsonStringify) {
1218 return safeJsonStringify(rec);
1219 } else {
1220 var dedupKey = e.stack.split(/\n/g, 3).join('\n');
1221 _warn('bunyan: ERROR: Exception in '
1222 + '`JSON.stringify(rec)`. You can install the '
1223 + '"safe-json-stringify" module to have Bunyan fallback '
1224 + 'to safer stringification. Record:\n'
1225 + _indent(format('%s\n%s', util.inspect(rec), e.stack)),
1226 dedupKey);
1227 return format('(Exception in JSON.stringify(rec): %j. '
1228 + 'See stderr for details.)', e.message);
1229 }
1230 }
1231 }
1232}
1233
1234
1235var RotatingFileStream = null;
1236if (mv) {
1237
1238RotatingFileStream = function RotatingFileStream(options) {
1239 this.path = options.path;
1240
1241 this.count = (options.count == null ? 10 : options.count);
1242 assert.equal(typeof (this.count), 'number',
1243 format('rotating-file stream "count" is not a number: %j (%s) in %j',
1244 this.count, typeof (this.count), this));
1245 assert.ok(this.count >= 0,
1246 format('rotating-file stream "count" is not >= 0: %j in %j',
1247 this.count, this));
1248
1249 // Parse `options.period`.
1250 if (options.period) {
1251 // <number><scope> where scope is:
1252 // h hours (at the start of the hour)
1253 // d days (at the start of the day, i.e. just after midnight)
1254 // w weeks (at the start of Sunday)
1255 // m months (on the first of the month)
1256 // y years (at the start of Jan 1st)
1257 // with special values 'hourly' (1h), 'daily' (1d), "weekly" (1w),
1258 // 'monthly' (1m) and 'yearly' (1y)
1259 var period = {
1260 'hourly': '1h',
1261 'daily': '1d',
1262 'weekly': '1w',
1263 'monthly': '1m',
1264 'yearly': '1y'
1265 }[options.period] || options.period;
1266 var m = /^([1-9][0-9]*)([hdwmy]|ms)$/.exec(period);
1267 if (!m) {
1268 throw new Error(format('invalid period: "%s"', options.period));
1269 }
1270 this.periodNum = Number(m[1]);
1271 this.periodScope = m[2];
1272 } else {
1273 this.periodNum = 1;
1274 this.periodScope = 'd';
1275 }
1276
1277 var lastModified = null;
1278 try {
1279 var fileInfo = fs.statSync(this.path);
1280 lastModified = fileInfo.mtime.getTime();
1281 }
1282 catch (err) {
1283 // file doesn't exist
1284 }
1285 var rotateAfterOpen = false;
1286 if (lastModified) {
1287 var lastRotTime = this._calcRotTime(0);
1288 if (lastModified < lastRotTime) {
1289 rotateAfterOpen = true;
1290 }
1291 }
1292
1293 // TODO: template support for backup files
1294 // template: <path to which to rotate>
1295 // default is %P.%n
1296 // '/var/log/archive/foo.log' -> foo.log.%n
1297 // '/var/log/archive/foo.log.%n'
1298 // codes:
1299 // XXX support strftime codes (per node version of those)
1300 // or whatever module. Pick non-colliding for extra
1301 // codes
1302 // %P `path` base value
1303 // %n integer number of rotated log (1,2,3,...)
1304 // %d datetime in YYYY-MM-DD_HH-MM-SS
1305 // XXX what should default date format be?
1306 // prior art? Want to avoid ':' in
1307 // filenames (illegal on Windows for one).
1308
1309 this.stream = fs.createWriteStream(this.path,
1310 {flags: 'a', encoding: 'utf8'});
1311
1312 this.rotQueue = [];
1313 this.rotating = false;
1314 if (rotateAfterOpen) {
1315 this._debug('rotateAfterOpen -> call rotate()');
1316 this.rotate();
1317 } else {
1318 this._setupNextRot();
1319 }
1320}
1321
1322util.inherits(RotatingFileStream, EventEmitter);
1323
1324RotatingFileStream.prototype._debug = function () {
1325 // Set this to `true` to add debug logging.
1326 if (false) {
1327 if (arguments.length === 0) {
1328 return true;
1329 }
1330 var args = Array.prototype.slice.call(arguments);
1331 args[0] = '[' + (new Date().toISOString()) + ', '
1332 + this.path + '] ' + args[0];
1333 console.log.apply(this, args);
1334 } else {
1335 return false;
1336 }
1337};
1338
1339RotatingFileStream.prototype._setupNextRot = function () {
1340 this.rotAt = this._calcRotTime(1);
1341 this._setRotationTimer();
1342}
1343
1344RotatingFileStream.prototype._setRotationTimer = function () {
1345 var self = this;
1346 var delay = this.rotAt - Date.now();
1347 // Cap timeout to Node's max setTimeout, see
1348 // <https://github.com/joyent/node/issues/8656>.
1349 var TIMEOUT_MAX = 2147483647; // 2^31-1
1350 if (delay > TIMEOUT_MAX) {
1351 delay = TIMEOUT_MAX;
1352 }
1353 this.timeout = setTimeout(
1354 function () {
1355 self._debug('_setRotationTimer timeout -> call rotate()');
1356 self.rotate();
1357 },
1358 delay);
1359 if (typeof (this.timeout.unref) === 'function') {
1360 this.timeout.unref();
1361 }
1362}
1363
1364RotatingFileStream.prototype._calcRotTime =
1365function _calcRotTime(periodOffset) {
1366 this._debug('_calcRotTime: %s%s', this.periodNum, this.periodScope);
1367 var d = new Date();
1368
1369 this._debug(' now local: %s', d);
1370 this._debug(' now utc: %s', d.toISOString());
1371 var rotAt;
1372 switch (this.periodScope) {
1373 case 'ms':
1374 // Hidden millisecond period for debugging.
1375 if (this.rotAt) {
1376 rotAt = this.rotAt + this.periodNum * periodOffset;
1377 } else {
1378 rotAt = Date.now() + this.periodNum * periodOffset;
1379 }
1380 break;
1381 case 'h':
1382 if (this.rotAt) {
1383 rotAt = this.rotAt + this.periodNum * 60 * 60 * 1000 * periodOffset;
1384 } else {
1385 // First time: top of the next hour.
1386 rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(),
1387 d.getUTCDate(), d.getUTCHours() + periodOffset);
1388 }
1389 break;
1390 case 'd':
1391 if (this.rotAt) {
1392 rotAt = this.rotAt + this.periodNum * 24 * 60 * 60 * 1000
1393 * periodOffset;
1394 } else {
1395 // First time: start of tomorrow (i.e. at the coming midnight) UTC.
1396 rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(),
1397 d.getUTCDate() + periodOffset);
1398 }
1399 break;
1400 case 'w':
1401 // Currently, always on Sunday morning at 00:00:00 (UTC).
1402 if (this.rotAt) {
1403 rotAt = this.rotAt + this.periodNum * 7 * 24 * 60 * 60 * 1000
1404 * periodOffset;
1405 } else {
1406 // First time: this coming Sunday.
1407 var dayOffset = (7 - d.getUTCDay());
1408 if (periodOffset < 1) {
1409 dayOffset = -d.getUTCDay();
1410 }
1411 if (periodOffset > 1 || periodOffset < -1) {
1412 dayOffset += 7 * periodOffset;
1413 }
1414 rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(),
1415 d.getUTCDate() + dayOffset);
1416 }
1417 break;
1418 case 'm':
1419 if (this.rotAt) {
1420 rotAt = Date.UTC(d.getUTCFullYear(),
1421 d.getUTCMonth() + this.periodNum * periodOffset, 1);
1422 } else {
1423 // First time: the start of the next month.
1424 rotAt = Date.UTC(d.getUTCFullYear(),
1425 d.getUTCMonth() + periodOffset, 1);
1426 }
1427 break;
1428 case 'y':
1429 if (this.rotAt) {
1430 rotAt = Date.UTC(d.getUTCFullYear() + this.periodNum * periodOffset,
1431 0, 1);
1432 } else {
1433 // First time: the start of the next year.
1434 rotAt = Date.UTC(d.getUTCFullYear() + periodOffset, 0, 1);
1435 }
1436 break;
1437 default:
1438 assert.fail(format('invalid period scope: "%s"', this.periodScope));
1439 }
1440
1441 if (this._debug()) {
1442 this._debug(' **rotAt**: %s (utc: %s)', rotAt,
1443 new Date(rotAt).toUTCString());
1444 var now = Date.now();
1445 this._debug(' now: %s (%sms == %smin == %sh to go)',
1446 now,
1447 rotAt - now,
1448 (rotAt-now)/1000/60,
1449 (rotAt-now)/1000/60/60);
1450 }
1451 return rotAt;
1452};
1453
1454RotatingFileStream.prototype.rotate = function rotate() {
1455 // XXX What about shutdown?
1456 var self = this;
1457
1458 // If rotation period is > ~25 days, we have to break into multiple
1459 // setTimeout's. See <https://github.com/joyent/node/issues/8656>.
1460 if (self.rotAt && self.rotAt > Date.now()) {
1461 return self._setRotationTimer();
1462 }
1463
1464 this._debug('rotate');
1465 if (self.rotating) {
1466 throw new TypeError('cannot start a rotation when already rotating');
1467 }
1468 self.rotating = true;
1469
1470 self.stream.end(); // XXX can do moves sync after this? test at high rate
1471
1472 function del() {
1473 var toDel = self.path + '.' + String(n - 1);
1474 if (n === 0) {
1475 toDel = self.path;
1476 }
1477 n -= 1;
1478 self._debug(' rm %s', toDel);
1479 fs.unlink(toDel, function (delErr) {
1480 //XXX handle err other than not exists
1481 moves();
1482 });
1483 }
1484
1485 function moves() {
1486 if (self.count === 0 || n < 0) {
1487 return finish();
1488 }
1489 var before = self.path;
1490 var after = self.path + '.' + String(n);
1491 if (n > 0) {
1492 before += '.' + String(n - 1);
1493 }
1494 n -= 1;
1495 fs.exists(before, function (exists) {
1496 if (!exists) {
1497 moves();
1498 } else {
1499 self._debug(' mv %s %s', before, after);
1500 mv(before, after, function (mvErr) {
1501 if (mvErr) {
1502 self.emit('error', mvErr);
1503 finish(); // XXX finish here?
1504 } else {
1505 moves();
1506 }
1507 });
1508 }
1509 })
1510 }
1511
1512 function finish() {
1513 self._debug(' open %s', self.path);
1514 self.stream = fs.createWriteStream(self.path,
1515 {flags: 'a', encoding: 'utf8'});
1516 var q = self.rotQueue, len = q.length;
1517 for (var i = 0; i < len; i++) {
1518 self.stream.write(q[i]);
1519 }
1520 self.rotQueue = [];
1521 self.rotating = false;
1522 self.emit('drain');
1523 self._setupNextRot();
1524 }
1525
1526 var n = this.count;
1527 del();
1528};
1529
1530RotatingFileStream.prototype.write = function write(s) {
1531 if (this.rotating) {
1532 this.rotQueue.push(s);
1533 return false;
1534 } else {
1535 return this.stream.write(s);
1536 }
1537};
1538
1539RotatingFileStream.prototype.end = function end(s) {
1540 this.stream.end();
1541};
1542
1543RotatingFileStream.prototype.destroy = function destroy(s) {
1544 this.stream.destroy();
1545};
1546
1547RotatingFileStream.prototype.destroySoon = function destroySoon(s) {
1548 this.stream.destroySoon();
1549};
1550
1551} /* if (mv) */
1552
1553
1554
1555/**
1556 * RingBuffer is a Writable Stream that just stores the last N records in
1557 * memory.
1558 *
1559 * @param options {Object}, with the following fields:
1560 *
1561 * - limit: number of records to keep in memory
1562 */
1563function RingBuffer(options) {
1564 this.limit = options && options.limit ? options.limit : 100;
1565 this.writable = true;
1566 this.records = [];
1567 EventEmitter.call(this);
1568}
1569
1570util.inherits(RingBuffer, EventEmitter);
1571
1572RingBuffer.prototype.write = function (record) {
1573 if (!this.writable)
1574 throw (new Error('RingBuffer has been ended already'));
1575
1576 this.records.push(record);
1577
1578 if (this.records.length > this.limit)
1579 this.records.shift();
1580
1581 return (true);
1582};
1583
1584RingBuffer.prototype.end = function () {
1585 if (arguments.length > 0)
1586 this.write.apply(this, Array.prototype.slice.call(arguments));
1587 this.writable = false;
1588};
1589
1590RingBuffer.prototype.destroy = function () {
1591 this.writable = false;
1592 this.emit('close');
1593};
1594
1595RingBuffer.prototype.destroySoon = function () {
1596 this.destroy();
1597};
1598
1599
1600//---- Exports
1601
1602module.exports = Logger;
1603
1604module.exports.TRACE = TRACE;
1605module.exports.DEBUG = DEBUG;
1606module.exports.INFO = INFO;
1607module.exports.WARN = WARN;
1608module.exports.ERROR = ERROR;
1609module.exports.FATAL = FATAL;
1610module.exports.resolveLevel = resolveLevel;
1611module.exports.levelFromName = levelFromName;
1612module.exports.nameFromLevel = nameFromLevel;
1613
1614module.exports.VERSION = VERSION;
1615module.exports.LOG_VERSION = LOG_VERSION;
1616
1617module.exports.createLogger = function createLogger(options) {
1618 return new Logger(options);
1619};
1620
1621module.exports.RingBuffer = RingBuffer;
1622module.exports.RotatingFileStream = RotatingFileStream;
1623
1624// Useful for custom `type == 'raw'` streams that may do JSON stringification
1625// of log records themselves. Usage:
1626// var str = JSON.stringify(rec, bunyan.safeCycles());
1627module.exports.safeCycles = safeCycles;