UNPKG

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