123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560 |
- /**
- * logger.js: TODO: add file header description.
- *
- * (C) 2010 Charlie Robbins
- * MIT LICENCE
- */
- 'use strict';
- const stream = require('readable-stream');
- const asyncForEach = require('async/forEach');
- const { LEVEL, SPLAT } = require('triple-beam');
- const isStream = require('is-stream');
- const ExceptionHandler = require('./exception-handler');
- const LegacyTransportStream = require('winston-transport/legacy');
- const Profiler = require('./profiler');
- const { clone, warn } = require('./common');
- const config = require('./config');
- /**
- * TODO: add class description.
- * @type {Logger}
- * @extends {stream.Transform}
- */
- class Logger extends stream.Transform {
- /**
- * Constructor function for the Logger object responsible for persisting log
- * messages and metadata to one or more transports.
- * @param {!Object} options - foo
- */
- constructor(options) {
- super({
- objectMode: true
- });
- this.configure(options);
- }
- /**
- * This will wholesale reconfigure this instance by:
- * 1. Resetting all transports. Older transports will be removed implicitly.
- * 2. Set all other options including levels, colors, rewriters, filters,
- * exceptionHandlers, etc.
- * @param {!Object} options - TODO: add param description.
- * @returns {undefined}
- */
- configure({
- silent,
- format,
- levels,
- level = 'info',
- exitOnError = true,
- transports,
- colors,
- emitErrs,
- formatters,
- padLevels,
- rewriters,
- stripColors,
- exceptionHandlers
- } = {}) {
- // Reset transports if we already have them
- if (this.transports.length) {
- this.clear();
- }
- this.silent = silent;
- this.format = format || this.format || require('logform/json')();
- // Hoist other options onto this instance.
- this.levels = levels || this.levels || config.npm.levels;
- this.level = level;
- this.exceptions = new ExceptionHandler(this);
- this.profilers = {};
- this.exitOnError = exitOnError;
- // Add all transports we have been provided.
- if (transports) {
- transports = Array.isArray(transports) ? transports : [transports];
- transports.forEach(transport => this.add(transport));
- }
- if (
- colors || emitErrs || formatters ||
- padLevels || rewriters || stripColors
- ) {
- throw new Error([
- '{ colors, emitErrs, formatters, padLevels, rewriters, stripColors } were removed in winston@3.0.0.',
- 'Use a custom winston.format(function) instead.',
- 'See: https://github.com/winstonjs/winston/tree/master/UPGRADE-3.0.md'
- ].join('\n'));
- }
- if (exceptionHandlers) {
- this.exceptions.handle(exceptionHandlers);
- }
- }
- isLevelEnabled(level) {
- const givenLevelValue = getLevelValue(this.levels, level);
- if (givenLevelValue === null) {
- return false;
- }
- const configuredLevelValue = getLevelValue(this.levels, this.level);
- if (configuredLevelValue === null) {
- return false;
- }
- if (!this.transports || this.transports.length === 0) {
- return configuredLevelValue >= givenLevelValue;
- }
- const index = this.transports.findIndex(transport => {
- let transportLevelValue = getLevelValue(this.levels, transport.level);
- if (transportLevelValue === null) {
- transportLevelValue = configuredLevelValue;
- }
- return transportLevelValue >= givenLevelValue;
- });
- return index !== -1;
- }
- /* eslint-disable valid-jsdoc */
- /**
- * Ensure backwards compatibility with a `log` method
- * @param {mixed} level - Level the log message is written at.
- * @param {mixed} msg - TODO: add param description.
- * @param {mixed} meta - TODO: add param description.
- * @returns {Logger} - TODO: add return description.
- *
- * @example
- * // Supports the existing API:
- * logger.log('info', 'Hello world', { custom: true });
- * logger.log('info', new Error('Yo, it\'s on fire'));
- * logger.log('info', '%s %d%%', 'A string', 50, { thisIsMeta: true });
- *
- * // And the new API with a single JSON literal:
- * logger.log({ level: 'info', message: 'Hello world', custom: true });
- * logger.log({ level: 'info', message: new Error('Yo, it\'s on fire') });
- * logger.log({
- * level: 'info',
- * message: '%s %d%%',
- * [SPLAT]: ['A string', 50],
- * meta: { thisIsMeta: true }
- * });
- *
- */
- /* eslint-enable valid-jsdoc */
- log(level, msg, ...splat) { // eslint-disable-line max-params
- // Optimize for the hotpath of logging JSON literals
- if (arguments.length === 1) {
- // Yo dawg, I heard you like levels ... seriously ...
- // In this context the LHS `level` here is actually the `info` so read
- // this as: info[LEVEL] = info.level;
- level[LEVEL] = level.level;
- this.write(level);
- return this;
- }
- // Slightly less hotpath, but worth optimizing for.
- if (arguments.length === 2) {
- if (msg && typeof msg === 'object') {
- msg[LEVEL] = msg.level = level;
- this.write(msg);
- return this;
- }
- this.write({ [LEVEL]: level, level, message: msg });
- return this;
- }
- const [meta] = splat;
- if (typeof meta === 'object' && meta !== null) {
- this.write(Object.assign({}, meta, {
- [LEVEL]: level,
- [SPLAT]: splat.slice(0),
- level,
- message: msg
- }));
- } else {
- this.write(Object.assign({}, {
- [LEVEL]: level,
- [SPLAT]: splat,
- level,
- message: msg
- }));
- }
- return this;
- }
- /**
- * Pushes data so that it can be picked up by all of our pipe targets.
- * @param {mixed} info - TODO: add param description.
- * @param {mixed} enc - TODO: add param description.
- * @param {mixed} callback - Continues stream processing.
- * @returns {undefined}
- * @private
- */
- _transform(info, enc, callback) {
- if (this.silent) {
- return callback();
- }
- // [LEVEL] is only soft guaranteed to be set here since we are a proper
- // stream. It is likely that `info` came in through `.log(info)` or
- // `.info(info)`. If it is not defined, however, define it.
- // This LEVEL symbol is provided by `triple-beam` and also used in:
- // - logform
- // - winston-transport
- // - abstract-winston-transport
- if (!info[LEVEL]) {
- info[LEVEL] = info.level;
- }
- // Remark: really not sure what to do here, but this has been reported as
- // very confusing by pre winston@2.0.0 users as quite confusing when using
- // custom levels.
- if (!this.levels[info[LEVEL]] && this.levels[info[LEVEL]] !== 0) {
- // eslint-disable-next-line no-console
- console.error('[winston] Unknown logger level: %s', info[LEVEL]);
- }
- // Remark: not sure if we should simply error here.
- if (!this._readableState.pipes) {
- // eslint-disable-next-line no-console
- console.error('[winston] Attempt to write logs with no transports %j', info);
- }
- // Here we write to the `format` pipe-chain, which on `readable` above will
- // push the formatted `info` Object onto the buffer for this instance. We trap
- // (and re-throw) any errors generated by the user-provided format, but also
- // guarantee that the streams callback is invoked so that we can continue flowing.
- try {
- this.push(this.format.transform(info, this.format.options));
- } catch (ex) {
- throw ex;
- } finally {
- // eslint-disable-next-line callback-return
- callback();
- }
- }
- /**
- * Delays the 'finish' event until all transport pipe targets have
- * also emitted 'finish' or are already finished.
- * @param {mixed} callback - Continues stream processing.
- */
- _final(callback) {
- const transports = this.transports.slice();
- asyncForEach(transports, (transport, next) => {
- if (!transport || transport.finished) return setImmediate(next);
- transport.once('finish', next);
- transport.end();
- }, callback);
- }
- /**
- * Adds the transport to this logger instance by piping to it.
- * @param {mixed} transport - TODO: add param description.
- * @returns {Logger} - TODO: add return description.
- */
- add(transport) {
- // Support backwards compatibility with all existing `winston < 3.x.x`
- // transports which meet one of two criteria:
- // 1. They inherit from winston.Transport in < 3.x.x which is NOT a stream.
- // 2. They expose a log method which has a length greater than 2 (i.e. more then
- // just `log(info, callback)`.
- const target = !isStream(transport) || transport.log.length > 2
- ? new LegacyTransportStream({ transport })
- : transport;
- if (!target._writableState || !target._writableState.objectMode) {
- throw new Error('Transports must WritableStreams in objectMode. Set { objectMode: true }.');
- }
- // Listen for the `error` event on the new Transport.
- this._onError(target);
- this.pipe(target);
- if (transport.handleExceptions) {
- this.exceptions.handle();
- }
- return this;
- }
- /**
- * Removes the transport from this logger instance by unpiping from it.
- * @param {mixed} transport - TODO: add param description.
- * @returns {Logger} - TODO: add return description.
- */
- remove(transport) {
- let target = transport;
- if (!isStream(transport) || transport.log.length > 2) {
- target = this.transports
- .filter(match => match.transport === transport)[0];
- }
- if (target) { this.unpipe(target); }
- return this;
- }
- /**
- * Removes all transports from this logger instance.
- * @returns {Logger} - TODO: add return description.
- */
- clear() {
- this.unpipe();
- return this;
- }
- /**
- * Cleans up resources (streams, event listeners) for all transports
- * associated with this instance (if necessary).
- * @returns {Logger} - TODO: add return description.
- */
- close() {
- this.clear();
- this.emit('close');
- return this;
- }
- /**
- * Sets the `target` levels specified on this instance.
- * @param {Object} Target levels to use on this instance.
- */
- setLevels() {
- warn.deprecated('setLevels');
- }
- /**
- * Queries the all transports for this instance with the specified `options`.
- * This will aggregate each transport's results into one object containing
- * a property per transport.
- * @param {Object} options - Query options for this instance.
- * @param {function} callback - Continuation to respond to when complete.
- * @retruns {mixed} - TODO: add return description.
- */
- query(options, callback) {
- if (typeof options === 'function') {
- callback = options;
- options = {};
- }
- options = options || {};
- const results = {};
- const queryObject = clone(options.query) || {};
- // Helper function to query a single transport
- function queryTransport(transport, next) {
- if (options.query) {
- options.query = transport.formatQuery(queryObject);
- }
- transport.query(options, (err, res) => {
- if (err) {
- return next(err);
- }
- next(null, transport.formatResults(res, options.format));
- });
- }
- // Helper function to accumulate the results from `queryTransport` into
- // the `results`.
- function addResults(transport, next) {
- queryTransport(transport, (err, result) => {
- // queryTransport could potentially invoke the callback multiple times
- // since Transport code can be unpredictable.
- if (next) {
- result = err || result;
- if (result) {
- results[transport.name] = result;
- }
- // eslint-disable-next-line callback-return
- next();
- }
- next = null;
- });
- }
- // Iterate over the transports in parallel setting the appropriate key in
- // the `results`.
- asyncForEach(
- this.transports.filter(transport => !!transport.query),
- addResults,
- () => callback(null, results)
- );
- }
- /**
- * Returns a log stream for all transports. Options object is optional.
- * @param{Object} options={} - Stream options for this instance.
- * @returns {Stream} - TODO: add return description.
- */
- stream(options = {}) {
- const out = new stream.Stream();
- const streams = [];
- out._streams = streams;
- out.destroy = () => {
- let i = streams.length;
- while (i--) {
- streams[i].destroy();
- }
- };
- // Create a list of all transports for this instance.
- this.transports
- .filter(transport => !!transport.stream)
- .forEach(transport => {
- const str = transport.stream(options);
- if (!str) {
- return;
- }
- streams.push(str);
- str.on('log', log => {
- log.transport = log.transport || [];
- log.transport.push(transport.name);
- out.emit('log', log);
- });
- str.on('error', err => {
- err.transport = err.transport || [];
- err.transport.push(transport.name);
- out.emit('error', err);
- });
- });
- return out;
- }
- /**
- * Returns an object corresponding to a specific timing. When done is called
- * the timer will finish and log the duration. e.g.:
- * @returns {Profile} - TODO: add return description.
- * @example
- * const timer = winston.startTimer()
- * setTimeout(() => {
- * timer.done({
- * message: 'Logging message'
- * });
- * }, 1000);
- */
- startTimer() {
- return new Profiler(this);
- }
- /**
- * Tracks the time inbetween subsequent calls to this method with the same
- * `id` parameter. The second call to this method will log the difference in
- * milliseconds along with the message.
- * @param {string} id Unique id of the profiler
- * @returns {Logger} - TODO: add return description.
- */
- profile(id, ...args) {
- const time = Date.now();
- if (this.profilers[id]) {
- const timeEnd = this.profilers[id];
- delete this.profilers[id];
- // Attempt to be kind to users if they are still using older APIs.
- if (typeof args[args.length - 2] === 'function') {
- // eslint-disable-next-line no-console
- console.warn('Callback function no longer supported as of winston@3.0.0');
- args.pop();
- }
- // Set the duration property of the metadata
- const info = typeof args[args.length - 1] === 'object' ? args.pop() : {};
- info.level = info.level || 'info';
- info.durationMs = time - timeEnd;
- info.message = info.message || id;
- return this.write(info);
- }
- this.profilers[id] = time;
- return this;
- }
- /**
- * Backwards compatibility to `exceptions.handle` in winston < 3.0.0.
- * @returns {undefined}
- * @deprecated
- */
- handleExceptions(...args) {
- // eslint-disable-next-line no-console
- console.warn('Deprecated: .handleExceptions() will be removed in winston@4. Use .exceptions.handle()');
- this.exceptions.handle(...args);
- }
- /**
- * Backwards compatibility to `exceptions.handle` in winston < 3.0.0.
- * @returns {undefined}
- * @deprecated
- */
- unhandleExceptions(...args) {
- // eslint-disable-next-line no-console
- console.warn('Deprecated: .unhandleExceptions() will be removed in winston@4. Use .exceptions.unhandle()');
- this.exceptions.unhandle(...args);
- }
- /**
- * Throw a more meaningful deprecation notice
- * @throws {Error} - TODO: add throws description.
- */
- cli() {
- throw new Error([
- 'Logger.cli() was removed in winston@3.0.0',
- 'Use a custom winston.formats.cli() instead.',
- 'See: https://github.com/winstonjs/winston/tree/master/UPGRADE-3.0.md'
- ].join('\n'));
- }
- /**
- * Bubbles the error, `err`, that occured on the specified `transport` up
- * from this instance if `emitErrs` has been set.
- * @param {Object} transport - Transport on which the error occured
- * @throws {Error} - Error that occurred on the transport
- * @private
- */
- _onError(transport) {
- function transportError(err) {
- this.emit('error', err, transport);
- }
- if (!transport.__winstonError) {
- transport.__winstonError = transportError.bind(this);
- transport.on('error', transport.__winstonError);
- }
- }
- }
- function getLevelValue(levels, level) {
- const value = levels[level];
- if (!value && value !== 0) {
- return null;
- }
- return value;
- }
- /**
- * Represents the current readableState pipe targets for this Logger instance.
- * @type {Array|Object}
- */
- Object.defineProperty(Logger.prototype, 'transports', {
- configurable: false,
- enumerable: true,
- get() {
- const { pipes } = this._readableState;
- return !Array.isArray(pipes) ? [pipes].filter(Boolean) : pipes;
- }
- });
- module.exports = Logger;
|