overleaf/libraries/logger/logging-manager.js

222 lines
6.4 KiB
JavaScript
Raw Normal View History

2019-03-06 15:08:38 -05:00
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS203: Remove `|| {}` from converted for-own loops
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const bunyan = require('bunyan');
const request = require('request');
const LOG_METHODS = [
'debug', 'info', 'log', 'error', 'err', 'warn', 'fatal'
]
class Logger {
constructor(name) {
const isProduction = (process.env['NODE_ENV'] || '').toLowerCase() === 'production';
this.defaultLevel = process.env['LOG_LEVEL'] || (isProduction ? "warn" : "debug");
this.loggerName = name;
this.logger = bunyan.createLogger({
name,
serializers: bunyan.stdSerializers,
level: this.defaultLevel
});
if (isProduction) {
// check for log level override on startup
this.checkLogLevel();
// re-check log level every minute
const checkLogLevel = () => this.checkLogLevel();
setInterval(checkLogLevel, 1000 * 60);
}
return this;
}
checkLogLevel() {
const options = {
headers: {
"Metadata-Flavor": "Google"
},
uri: `http://metadata.google.internal/computeMetadata/v1/project/attributes/${this.loggerName}-setLogLevelEndTime`
};
return request(options, (err, response, body) => {
if (parseInt(body) > Date.now()) {
return this.logger.level('trace');
} else {
return this.logger.level(this.defaultLevel);
}
});
}
initializeErrorReporting(sentry_dsn, options) {
const raven = require("raven");
this.raven = new raven.Client(sentry_dsn, options);
this.lastErrorTimeStamp = 0; // for rate limiting on sentry reporting
return this.lastErrorCount = 0;
}
captureException(attributes, message, level) {
// handle case of logger.error "message"
let key, value;
if (typeof attributes === 'string') {
attributes = {err: new Error(attributes)};
}
// extract any error object
let error = attributes.err || attributes.error;
// avoid reporting errors twice
for (key in attributes) {
value = attributes[key];
if (value instanceof Error && value.reportedToSentry) { return; }
}
// include our log message in the error report
if ((error == null)) {
if (typeof message === 'string') { error = {message}; }
} else if (message != null) {
attributes.description = message;
}
// report the error
if (error != null) {
// capture attributes and use *_id objects as tags
const tags = {};
const extra = {};
for (key in attributes) {
value = attributes[key];
if (key.match(/_id/) && (typeof value === 'string')) { tags[key] = value; }
extra[key] = value;
}
// capture req object if available
const { req } = attributes;
if (req != null) {
extra.req = {
method: req.method,
url: req.originalUrl,
query: req.query,
headers: req.headers,
ip: req.ip
};
}
// recreate error objects that have been converted to a normal object
if (!(error instanceof Error) && (typeof error === "object")) {
const newError = new Error(error.message);
for (key of Object.keys(error || {})) {
value = error[key];
newError[key] = value;
}
error = newError;
}
// filter paths from the message to avoid duplicate errors in sentry
// (e.g. errors from `fs` methods which have a path attribute)
try {
if (error.path) { error.message = error.message.replace(` '${error.path}'`, ''); }
} catch (error1) {}
// send the error to sentry
try {
this.raven.captureException(error, {tags, extra, level});
// put a flag on the errors to avoid reporting them multiple times
return (() => {
const result = [];
for (key in attributes) {
value = attributes[key];
if (value instanceof Error) { result.push(value.reportedToSentry = true); } else {
result.push(undefined);
}
}
return result;
})();
} catch (error2) {
return;
}
}
}
debug() {
return this.logger.debug.apply(this.logger, arguments);
}
info(){
return this.logger.info.apply(this.logger, arguments);
}
log(){
return this.logger.info.apply(this.logger, arguments);
}
error(attributes, message, ...args){
this.logger.error(attributes, message, ...Array.from(args));
if (this.raven != null) {
const MAX_ERRORS = 5; // maximum number of errors in 1 minute
const now = new Date();
// have we recently reported an error?
const recentSentryReport = (now - this.lastErrorTimeStamp) < (60 * 1000);
// if so, increment the error count
if (recentSentryReport) {
this.lastErrorCount++;
} else {
this.lastErrorCount = 0;
this.lastErrorTimeStamp = now;
}
// only report 5 errors every minute to avoid overload
if (this.lastErrorCount < MAX_ERRORS) {
// add a note if the rate limit has been hit
const note = (this.lastErrorCount+1) === MAX_ERRORS ? "(rate limited)" : "";
// report the exception
return this.captureException(attributes, message, `error${note}`);
}
}
}
err() {
return this.error.apply(this, arguments);
}
warn(){
return this.logger.warn.apply(this.logger, arguments);
}
fatal(attributes, message, callback) {
if (callback == null) { callback = function() {}; }
this.logger.fatal(attributes, message);
if (this.raven != null) {
var cb = function(e) { // call the callback once after 'logged' or 'error' event
callback();
return cb = function() {};
};
this.captureException(attributes, message, "fatal");
this.raven.once('logged', cb);
return this.raven.once('error', cb);
} else {
return callback();
}
}
}
let defaultLogger
// initialize default logger if not already initialized
if (!global.__logger_sharelatex__default_logger__) {
global.__logger_sharelatex__default_logger__ = defaultLogger = new Logger("default-sharelatex")
}
else {
defaultLogger = global.__logger_sharelatex__default_logger__
}
// support old interface for creating new Logger instances
Logger.initialize = function initialize(name) {
return new Logger(name)
}
// add a static method for each log method that will use the default logger
for (const logMethod of LOG_METHODS) {
Logger[logMethod] = function () {
return defaultLogger[logMethod].apply(defaultLogger, arguments)
}
}
// return default logger
Logger.defaultLogger = function defaultLogger () {
return defaultLogger
}
module.exports = Logger