Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bunyan.js 48KB

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