0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00
ghost/core/server/logging/GhostLogger.js
Katharina Irrgang 0e13ef8767 🎨 logging improvements (#7597)
* 🎨  rotation config
  - every parameter is configureable
  - increase default number of files to 100
* 🎨  ghost.log location
  - example: content/logs/http___my_ghost_blog_com_ghost.log
  - user can change the path to something custom by setting logging.path
* 🛠   add response-time as dependency
* 🎨  readable PrettyStream
  - tidy up
  - generic handling (was important to support more use cases, for example: logging.info({ anyKey: anyValue }))
  - common log format
  - less code 🕵🏻
* 🎨  GhostLogger cleanup
  - remove setLoggers -> this function had too much of redundant code
  - instead: add smart this.log function
  - remove logging.request (---> GhostLogger just forwards the values, it doesn't matter if that is a request or not a request)
  - make .warn .debug .info .error small and smart
* 🎨  app.js: add response time as middleware and remove logging.request
* 🎨  setStdoutStream and setFileStream
  - redesign GhostLogger to add CustomLoggers very easily

----> Example CustomLogger

function CustomLogger(options) {
  // Base iterates over defined transports
  // EXAMPLE: ['stdout', 'elasticsearch']
  Base.call(this, options);
}
util.inherits(...);

// OVERRIDE default stdout stream and your own!!!
CustomLogger.prototype.setStdoutStream = function() {}

// add a new stream
// get's called automatically when transport elasticsearch is defined
CustomLogger.prototype.setElasticsearchStream = function() {}

* 🎨  log into multiple file by default
  - content/logs/domain.error.log --> contains only the errors
  - content/logs/domain.log --> contains everything
  - rotation for both files
* 🔥  remove logging.debug and use npm debug only
*   shortcuts for mode and level
* 🎨  jshint/jscs
* 🎨  stdout as much as possible for an error
* 🎨  fix tests
* 🎨  remove req.ip from log output, remove response-time dependency
* 🎨  create middleware for logging
  - added TODO to move logging middleware to ignition
2016-10-25 12:17:43 +01:00

223 lines
6.1 KiB
JavaScript

var bunyan = require('bunyan'),
_ = require('lodash'),
GhostPrettyStream = require('./PrettyStream');
function GhostLogger(options) {
var self = this;
this.env = options.env;
this.domain = options.domain || 'localhost';
this.transports = options.transports || ['stdout'];
this.level = options.level || 'info';
this.mode = options.mode || 'short';
this.path = options.path || process.cwd();
this.rotation = options.rotation || {
enabled: false,
period: '1w',
count: 100
};
this.streams = {};
this.setSerializers();
_.each(this.transports, function (transport) {
self['set' + transport.slice(0, 1).toUpperCase() + transport.slice(1) + 'Stream']();
});
}
GhostLogger.prototype.setStdoutStream = function () {
var prettyStdOut = new GhostPrettyStream({
mode: this.mode
});
prettyStdOut.pipe(process.stdout);
this.streams.stdout = {
name: 'stdout',
log: bunyan.createLogger({
name: 'Log',
streams: [{
type: 'raw',
stream: prettyStdOut,
level: this.level
}],
serializers: this.serializers
})
};
};
/**
* by default we log into two files
* 1. file-errors: all errors only
* 2. file-all: everything
*/
GhostLogger.prototype.setFileStream = function () {
this.streams['file-errors'] = {
name: 'file',
log: bunyan.createLogger({
name: 'Log',
streams: [{
path: this.path + this.domain + '_' + '.error.log',
level: 'error'
}],
serializers: this.serializers
})
};
this.streams['file-all'] = {
name: 'file',
log: bunyan.createLogger({
name: 'Log',
streams: [{
path: this.path + this.domain + '_' + '.log',
level: this.level
}],
serializers: this.serializers
})
};
if (this.rotation.enabled) {
this.streams['rotation-errors'] = {
name: 'rotation-errors',
log: bunyan.createLogger({
name: 'Log',
streams: [{
type: 'rotating-file',
path: this.path + this.domain + '_' + '.error.log',
period: this.rotation.period,
count: this.rotation.count,
level: this.level
}],
serializers: this.serializers
})
};
this.streams['rotation-all'] = {
name: 'rotation-all',
log: bunyan.createLogger({
name: 'Log',
streams: [{
type: 'rotating-file',
path: this.path + this.domain + '_' + '.log',
period: this.rotation.period,
count: this.rotation.count,
level: this.level
}],
serializers: this.serializers
})
};
}
};
// @TODO: add correlation identifier
// @TODO: res.on('finish') has no access to the response body
GhostLogger.prototype.setSerializers = function setSerializers() {
var self = this;
this.serializers = {
req: function (req) {
return {
meta: {
requestId: req.requestId,
userId: req.userId
},
url: req.url,
method: req.method,
originalUrl: req.originalUrl,
params: req.params,
headers: self.removeSensitiveData(req.headers),
body: self.removeSensitiveData(req.body),
query: self.removeSensitiveData(req.query)
};
},
res: function (res) {
return {
_headers: self.removeSensitiveData(res._headers),
statusCode: res.statusCode,
responseTime: res.responseTime
};
},
err: function (err) {
return {
name: err.errorType,
statusCode: err.statusCode,
level: err.level,
message: err.message,
context: err.context,
help: err.help,
stack: err.stack,
hideStack: err.hideStack
};
}
};
};
GhostLogger.prototype.removeSensitiveData = function removeSensitiveData(obj) {
var newObj = {};
_.each(obj, function (value, key) {
if (!key.match(/pin|password|authorization|cookie/gi)) {
newObj[key] = value;
}
});
return newObj;
};
/**
* Because arguments can contain lot's of different things, we prepare the arguments here.
* This function allows us to use logging very flexible!
*
* logging.info('HEY', 'DU') --> is one string
* logging.info({}, {}) --> is one object
* logging.error(new Error()) --> is {err: new Error()}
*/
GhostLogger.prototype.log = function log(type, args) {
var self = this,
modifiedArguments;
_.each(args, function (value) {
if (value instanceof Error) {
if (!modifiedArguments) {
modifiedArguments = {};
}
modifiedArguments.err = value;
} else if (_.isObject(value)) {
if (!modifiedArguments) {
modifiedArguments = {};
}
var keys = Object.keys(value);
_.each(keys, function (key) {
modifiedArguments[key] = value[key];
});
} else {
if (!modifiedArguments) {
modifiedArguments = '';
}
modifiedArguments += value;
modifiedArguments += ' ';
}
});
_.each(self.streams, function (logger) {
logger.log[type](modifiedArguments);
});
};
GhostLogger.prototype.info = function info() {
this.log('info', arguments);
};
GhostLogger.prototype.warn = function warn() {
this.log('warn', arguments);
};
GhostLogger.prototype.error = function error() {
this.log('error', arguments);
};
module.exports = GhostLogger;