277 lines
8.3 KiB
JavaScript
277 lines
8.3 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const formatters = require('./formatters');
|
|
|
|
const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'];
|
|
const DEFAULT_LEVEL = 'INFO';
|
|
|
|
module.exports = logops();
|
|
|
|
function noop() {};
|
|
|
|
function logops(options) {
|
|
let opts = merge({
|
|
level: process.env.LOGOPS_LEVEL || DEFAULT_LEVEL,
|
|
format: formatters[process.env.LOGOPS_FORMAT] || (process.env.NODE_ENV === 'development' ?
|
|
formatters.dev :
|
|
formatters.json
|
|
),
|
|
getContext: noop,
|
|
stream: process.stdout,
|
|
}, options);
|
|
|
|
let API = {};
|
|
/**
|
|
* Internal private function that implements a decorator to all
|
|
* the level functions.
|
|
*
|
|
* @param {String} level one of
|
|
* ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
|
|
*/
|
|
function logWrap(level) {
|
|
return function log() {
|
|
let context, message, args, trace, err;
|
|
|
|
if (arguments[0] instanceof Error) {
|
|
// log.<level>(err, ...)
|
|
context = API.getContext();
|
|
args = Array.prototype.slice.call(arguments, 1);
|
|
if (!args.length) {
|
|
// log.<level>(err)
|
|
err = arguments[0];
|
|
message = err.name + ': ' + err.message;
|
|
} else {
|
|
// log.<level>(err, "More %s", "things")
|
|
// Use the err as context information
|
|
err = arguments[0];
|
|
message = arguments[1];
|
|
args = Array.prototype.slice.call(args, 1);
|
|
}
|
|
} else if (arguments[0] == null || (typeof (arguments[0]) !== 'object' && arguments[0] !== null) ||
|
|
Array.isArray(arguments[0])) {
|
|
// log.<level>(msg, ...)
|
|
context = API.getContext();
|
|
message = arguments[0];
|
|
args = Array.prototype.slice.call(arguments, 1);
|
|
} else {
|
|
// log.<level>(fields, msg, ...)
|
|
context = merge(API.getContext(), arguments[0]);
|
|
message = arguments[1];
|
|
args = Array.prototype.slice.call(arguments, 2);
|
|
}
|
|
|
|
trace = API.format(level, context || {}, message, args, err);
|
|
API.stream.write(trace + '\n');
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Sets the enabled logging level.
|
|
* All the disabled logging methods are replaced by a noop,
|
|
* so there is not any performance penalty at production using an undesired level
|
|
*
|
|
* @param {String} level
|
|
*/
|
|
function setLevel(level) {
|
|
opts.level = level;
|
|
let logLevelIndex = levels.indexOf(opts.level.toUpperCase());
|
|
|
|
levels.forEach((logLevel) => {
|
|
let fn;
|
|
if (logLevelIndex <= levels.indexOf(logLevel)) {
|
|
fn = logWrap(logLevel);
|
|
} else {
|
|
fn = noop;
|
|
}
|
|
API[logLevel.toLowerCase()] = fn;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The exported API.
|
|
* The following methods are added dynamically
|
|
* API.debug
|
|
* API.info
|
|
* API.warn
|
|
* API.error
|
|
* API.fatal
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
API = {
|
|
/**
|
|
* The stream where the logger will write string traces
|
|
* Defaults to process.stdout
|
|
*/
|
|
stream: opts.stream,
|
|
setStream: (stream) => {
|
|
API.stream = stream;
|
|
},
|
|
|
|
/**
|
|
* Sets the enabled logging level.
|
|
* All the disabled logging methods are replaced by a noop,
|
|
* so there is not any performance penalty at production using an undesired level
|
|
*
|
|
* @param {String} level one of the following values
|
|
* ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
|
|
*/
|
|
setLevel: setLevel,
|
|
|
|
/**
|
|
* Gets the current log level.
|
|
*/
|
|
getLevel: () => opts.level,
|
|
|
|
/**
|
|
* Gets the context for a determinate trace. By default, this is a noop that
|
|
* you can override if you are managing your execution context with node domains
|
|
*
|
|
* This function must return an object with the following fields
|
|
* {
|
|
* corr: {String},
|
|
* trans: {String},
|
|
* op: {String}
|
|
* }
|
|
*
|
|
* If you are not using domain, you should pass the context information for
|
|
* EVERY log call
|
|
*
|
|
* Both examples will produce the same trace
|
|
*
|
|
* Example usage not using getContext:
|
|
* var logger = require('logops');
|
|
* req.context = {
|
|
* corr: 'cbefb082-3429-4f5c-aafd-26b060d6a9fc',
|
|
* trans: 'cbefb082-3429-4f5c-aafd-26b060d6a9fc',
|
|
* op: 'SendEMail'
|
|
* }
|
|
* logger.info(req.context, 'This is an example');
|
|
*
|
|
* Example using this feature:
|
|
* var logger = require('logops'),
|
|
* domain = require('domain');
|
|
*
|
|
* logger.getContext = function domainContext() {
|
|
* return domain.active.myCustomContext;
|
|
* }
|
|
* //...
|
|
*
|
|
* logger.info('This is an example');
|
|
*
|
|
* @return {Object} The context object
|
|
*/
|
|
getContext: opts.getContext,
|
|
setContextGetter: (fn) => {
|
|
API.getContext = fn;
|
|
},
|
|
|
|
/**
|
|
* Creates a string representation for a trace.
|
|
*
|
|
* It checks the `LOGOPS_FORMAT` environment variable to use the built-in
|
|
* format functions. It fallbacks to check the de-facto `NODE_ENV` env var
|
|
* to use the `formatters.dev` when the value is `development`. Otherwise, it
|
|
* will use the `formatters.pipe` (while in production, for example)
|
|
*
|
|
* Example
|
|
* NODE_ENV=development node index.js
|
|
* the logger will write traces for developers
|
|
*
|
|
* node index.js
|
|
* the logger will write traces in a pipe format (assuming NODE_ENV nor
|
|
* LOGOPS_FORMAT environment vars are defined with valid values)
|
|
*
|
|
* LOGOPS_FORMAT=json node index.js
|
|
* the logger will write json traces
|
|
*
|
|
* You can override this func and manage by yourself the formatting.
|
|
*
|
|
* @param {String} level One of the following values
|
|
* ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
|
|
* @param {Object} context Additional information to add to the trace
|
|
* @param {String} message The main message to be added to the trace
|
|
* @param {Array} args More arguments provided to the log function
|
|
*
|
|
* @return {String} The trace formatted
|
|
*/
|
|
format: opts.format,
|
|
setFormat: (format) => {
|
|
API.format = format;
|
|
},
|
|
/**
|
|
* Return an Object containing the available formatters ("dev", "pipe", "json").
|
|
*
|
|
* Example using this feature to write JSON logs.
|
|
*
|
|
* var logger = require('logops');
|
|
* logger.format = logger.formatters.json;
|
|
* logger.info('This is an example')
|
|
*
|
|
* @return {Object} The available formatters.
|
|
*/
|
|
formatters: formatters,
|
|
|
|
/**
|
|
* Creates a child logger with the this logger settings that will append the
|
|
* passed context to the parent one (if any)
|
|
* ```
|
|
* let child = logger.child({hostname: 'host.local'});
|
|
* child.info({app: 'server'}, 'Startup');
|
|
* // {"hostname":"host.local","app":"server","time":"2015-12-23T11:47:25.862Z","lvl":"INFO","msg":"Startup"}
|
|
* ```
|
|
*/
|
|
child: (localContext) => {
|
|
// the current limitation for this approach is that child
|
|
// loggers will overwrite the parent and localcontext when
|
|
// calling againg `child.setContextGetter(fn)` or assign a function
|
|
// to `child.getContext = fn`
|
|
// But as the context is specified then creating the child, we can
|
|
// considerer it a corner case and dont optimize for the use case
|
|
return logops({
|
|
level: API.getLevel(),
|
|
getContext: () => merge(API.getContext(), localContext),
|
|
format: API.format,
|
|
stream: API.stream,
|
|
});
|
|
}
|
|
};
|
|
|
|
setLevel(opts.level);
|
|
return API;
|
|
}
|
|
|
|
/**
|
|
* Merges accesible properties in two objects.
|
|
* obj2 takes precedence when common properties are found
|
|
*
|
|
* @param {Object} obj1
|
|
* @param {Object} obj2
|
|
* @returns {{}} The merged, new, object
|
|
*/
|
|
function merge(obj1, obj2) {
|
|
var res = {}, attrname;
|
|
|
|
for (attrname in obj1) {
|
|
res[attrname] = obj1[attrname];
|
|
}
|
|
for (attrname in obj2) {
|
|
res[attrname] = obj2[attrname];
|
|
}
|
|
return res;
|
|
}
|