overleaf/libraries/logger/logging-manager.js

221 lines
6.3 KiB
JavaScript
Raw Normal View History

const bunyan = require("bunyan");
const request = require("request");
Logger = module.exports = {
initialize(name) {
this.isProduction =
(process.env["NODE_ENV"] || "").toLowerCase() === "production";
this.defaultLevel =
process.env["LOG_LEVEL"] || (this.isProduction ? "warn" : "debug");
this.loggerName = name;
this.ringBuffer = new bunyan.RingBuffer({
limit: process.env["LOG_RING_BUFFER_SIZE"] || 30
});
this.logger = bunyan.createLogger({
name,
serializers: bunyan.stdSerializers,
streams: [
{
level: this.defaultLevel,
stream: process.stdout
},
{
level: "trace",
type: "raw",
stream: this.ringBuffer
}
]
});
if (this.isProduction) {
// clear interval if already set
if (this.checkInterval) {
clearInterval(this.checkInterval);
}
// check for log level override on startup
this.checkLogLevel();
// re-check log level every minute
const checkLogLevel = () => this.checkLogLevel();
this.checkInterval = 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`
};
request(options, (err, response, body) => {
if (parseInt(body) > Date.now()) {
this.logger.level("trace");
} else {
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
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) {
if (this.isProduction) {
attributes.logBuffer = this.ringBuffer.records;
}
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();
}
}
};
Logger.initialize("default-sharelatex");