0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00
ghost/core/server/logging/GhostLogger.js
Katharina Irrgang 1882278b5b 🎨 configurable logging with bunyan (#7431)
- 🛠  add bunyan and prettyjson, remove morgan

-   add logging module
  - GhostLogger class that handles setup of bunyan
  - PrettyStream for stdout

-   config for logging
  - @TODO: testing level fatal?

-   log each request via GhostLogger (express middleware)
  - @TODO: add errors to output

- 🔥  remove errors.updateActiveTheme
  - we can read the value from config

- 🔥  remove 15 helper functions in core/server/errors/index.js
  - all these functions get replaced by modules:
    1. logging
    2. error middleware handling for html/json
    3. error creation (which will be part of PR #7477)

-   add express error handler for html/json
  - one true error handler for express responses
  - contains still some TODO's, but they are not high priority for first implementation/integration
  - this middleware only takes responsibility of either rendering html responses or return json error responses

- 🎨  use new express error handler in middleware/index
  - 404 and 500 handling

- 🎨  return error instead of error message in permissions/index.js
  - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error

- 🎨  wrap serve static module
  - rule: if you call a module/unit, you should always wrap this error
  - it's always the same rule
  - so the caller never has to worry about what comes back
  - it's always a clear error instance
  - in this case: we return our notfounderror if serve static does not find the resource
  - this avoid having checks everywhere

- 🎨  replace usages of errors/index.js functions and adapt tests
  - use logging.error, logging.warn
  - make tests green
  - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically

- 🐛  return errorDetails to Ghost-Admin
  - errorDetails is used for Theme error handling

- 🎨  use 500er error for theme is missing error in theme-handler

- 🎨  extend file rotation to 1w
2016-10-04 16:33:43 +01:00

210 lines
5.3 KiB
JavaScript

var bunyan = require('bunyan'),
_ = require('lodash'),
GhostPrettyStream = require('./PrettyStream');
function GhostLogger(options) {
this.env = options.env;
this.transports = options.transports || ['stdout'];
this.level = options.level || 'info';
this.mode = options.mode || 'short';
this.path = options.path || 'ghost.log';
this.rotation = options.rotation || false;
this.loggers = {};
this.setSerializers();
this.setLoggers();
this.setStreams();
}
// @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 {
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
};
},
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.setLoggers = function setLoggers() {
var self = this;
this.log = {
info: function (options) {
var req = options.req,
res = options.res;
_.each(self.loggers, function (logger) {
logger.log.info({
req: req,
res: res
});
});
},
debug: function (options) {
var req = options.req,
res = options.res;
_.each(self.loggers, function (logger) {
logger.log.debug({
req: req,
res: res
});
});
},
error: function (options) {
var req = options.req,
res = options.res,
err = options.err;
_.each(self.loggers, function (logger) {
logger.log.error({
req: req,
res: res,
err: err
});
});
}
};
};
GhostLogger.prototype.setStreams = function setStreams() {
var self = this,
streams = [],
prettyStdOut;
_.each(self.transports, function (transport) {
if (transport === 'file') {
streams.push({
name: 'file',
stream: {
path: self.path,
level: self.level
}
});
}
if (transport === 'stdout') {
prettyStdOut = new GhostPrettyStream();
prettyStdOut.pipe(process.stdout);
streams.push({
name: 'stdout',
stream: {
type: 'raw',
stream: prettyStdOut,
level: self.level
}
});
}
});
if (self.rotation) {
streams.push({
name: 'rotation',
stream: {
type: 'rotating-file',
path: self.path,
period: '1w',
count: 3,
level: self.level
}
});
}
// the env defines which streams are available
_.each(streams, function (stream) {
self.loggers[stream.name] = {
name: stream.name,
log: bunyan.createLogger({
name: 'Log',
streams: [stream.stream],
serializers: self.serializers
})
};
});
};
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;
};
GhostLogger.prototype.info = function info() {
var print = '';
_.each(arguments, function (value) {
print += value;
print += ' ';
});
this.loggers.stdout.log.info(print);
};
GhostLogger.prototype.warn = function warn() {
var print = '';
_.each(arguments, function (value) {
print += value;
print += ' ';
});
this.loggers.stdout.log.warn(print);
};
GhostLogger.prototype.debug = function debug(options) {
this.loggers.stdout.log.debug(options);
};
GhostLogger.prototype.error = function error(err) {
this.log.error({err: err});
};
GhostLogger.prototype.request = function request(options) {
var req = options.req,
res = options.res,
err = options.err;
if (err) {
this.log.error({req: req, res: res, err: err});
} else {
this.log.info({req: req, res: res});
}
};
module.exports = GhostLogger;