overleaf/libraries/logger/logging-manager.js

232 lines
6.6 KiB
JavaScript
Raw Normal View History

2019-03-08 11:06:24 -05:00
const bunyan = require('bunyan')
const request = require('request')
const Logger = module.exports = {
initialize(name) {
this.isProduction =
2019-03-08 11:06:24 -05:00
(process.env['NODE_ENV'] || '').toLowerCase() === 'production'
this.defaultLevel =
2019-03-08 11:06:24 -05:00
process.env['LOG_LEVEL'] || (this.isProduction ? 'warn' : 'debug')
this.loggerName = name
2019-03-11 10:27:35 -04:00
this.ringBufferSize = parseInt(process.env['LOG_RING_BUFFER_SIZE']) || 0
const loggerStreams = [
{
level: this.defaultLevel,
stream: process.stdout
}
]
if (this.ringBufferSize > 0) {
this.ringBuffer = new bunyan.RingBuffer({limit: this.ringBufferSize})
loggerStreams.push({
level: 'trace',
type: 'raw',
stream: this.ringBuffer
})
}
else {
this.ringBuffer = null
}
this.logger = bunyan.createLogger({
name,
serializers: bunyan.stdSerializers,
2019-03-11 10:27:35 -04:00
streams: loggerStreams
2019-03-08 11:06:24 -05:00
})
if (this.isProduction) {
// clear interval if already set
if (this.checkInterval) {
2019-03-08 11:06:24 -05:00
clearInterval(this.checkInterval)
}
// check for log level override on startup
2019-03-08 11:06:24 -05:00
this.checkLogLevel()
// re-check log level every minute
2019-03-08 11:06:24 -05:00
const checkLogLevel = () => this.checkLogLevel()
this.checkInterval = setInterval(checkLogLevel, 1000 * 60)
}
2019-03-08 11:06:24 -05:00
return this
},
checkLogLevel() {
const options = {
headers: {
2019-03-08 11:06:24 -05:00
'Metadata-Flavor': 'Google'
},
uri: `http://metadata.google.internal/computeMetadata/v1/project/attributes/${
this.loggerName
}-setLogLevelEndTime`
2019-03-08 11:06:24 -05:00
}
request(options, (err, response, body) => {
if (err) {
2019-03-11 10:27:35 -04:00
this.logger.level(this.defaultLevel)
return
}
if (parseInt(body) > Date.now()) {
2019-03-08 11:06:24 -05:00
this.logger.level('trace')
} else {
2019-03-08 11:06:24 -05:00
this.logger.level(this.defaultLevel)
}
2019-03-08 11:06:24 -05:00
})
},
initializeErrorReporting(sentry_dsn, options) {
2019-03-08 11:06:24 -05:00
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"
2019-03-08 11:06:24 -05:00
let key, value
if (typeof attributes === 'string') {
attributes = { err: new Error(attributes) }
}
// extract any error object
2019-03-08 11:06:24 -05:00
let error = attributes.err || attributes.error
// avoid reporting errors twice
for (key in attributes) {
2019-03-08 11:06:24 -05:00
value = attributes[key]
if (value instanceof Error && value.reportedToSentry) {
2019-03-08 11:06:24 -05:00
return
}
}
// include our log message in the error report
if (error == null) {
2019-03-08 11:06:24 -05:00
if (typeof message === 'string') {
error = { message }
}
} else if (message != null) {
2019-03-08 11:06:24 -05:00
attributes.description = message
}
// report the error
if (error != null) {
// capture attributes and use *_id objects as tags
2019-03-08 11:06:24 -05:00
const tags = {}
const extra = {}
for (key in attributes) {
2019-03-08 11:06:24 -05:00
value = attributes[key]
if (key.match(/_id/) && typeof value === 'string') {
tags[key] = value
}
2019-03-08 11:06:24 -05:00
extra[key] = value
}
// capture req object if available
2019-03-08 11:06:24 -05:00
const { req } = attributes
if (req != null) {
extra.req = {
method: req.method,
url: req.originalUrl,
query: req.query,
headers: req.headers,
ip: req.ip
2019-03-08 11:06:24 -05:00
}
}
// recreate error objects that have been converted to a normal object
2019-03-08 11:06:24 -05:00
if (!(error instanceof Error) && typeof error === 'object') {
const newError = new Error(error.message)
for (key of Object.keys(error || {})) {
2019-03-08 11:06:24 -05:00
value = error[key]
newError[key] = value
}
2019-03-08 11:06:24 -05:00
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) {
2019-03-08 11:06:24 -05:00
error.message = error.message.replace(` '${error.path}'`, '')
}
} catch (error1) {}
// send the error to sentry
try {
2019-03-08 11:06:24 -05:00
this.raven.captureException(error, { tags, extra, level })
// put a flag on the errors to avoid reporting them multiple times
return (() => {
2019-03-08 11:06:24 -05:00
const result = []
for (key in attributes) {
2019-03-08 11:06:24 -05:00
value = attributes[key]
if (value instanceof Error) {
2019-03-08 11:06:24 -05:00
result.push((value.reportedToSentry = true))
} else {
2019-03-08 11:06:24 -05:00
result.push(undefined)
}
}
2019-03-08 11:06:24 -05:00
return result
})()
} catch (error2) {
2019-03-08 11:06:24 -05:00
return
}
}
},
debug() {
2019-03-08 11:06:24 -05:00
return this.logger.debug.apply(this.logger, arguments)
},
info() {
2019-03-08 11:06:24 -05:00
return this.logger.info.apply(this.logger, arguments)
},
log() {
2019-03-08 11:06:24 -05:00
return this.logger.info.apply(this.logger, arguments)
},
error(attributes, message, ...args) {
2019-03-12 08:23:37 -04:00
if (this.ringBuffer !== null && Array.isArray(this.ringBuffer.records)) {
attributes.logBuffer = this.ringBuffer.records.filter(function (record) {
return record.level !== 50
})
}
2019-03-08 11:06:24 -05:00
this.logger.error(attributes, message, ...Array.from(args))
if (this.raven != null) {
2019-03-08 11:06:24 -05:00
const MAX_ERRORS = 5 // maximum number of errors in 1 minute
const now = new Date()
// have we recently reported an error?
2019-03-08 11:06:24 -05:00
const recentSentryReport = now - this.lastErrorTimeStamp < 60 * 1000
// if so, increment the error count
if (recentSentryReport) {
2019-03-08 11:06:24 -05:00
this.lastErrorCount++
} else {
2019-03-08 11:06:24 -05:00
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 =
2019-03-08 11:06:24 -05:00
this.lastErrorCount + 1 === MAX_ERRORS ? '(rate limited)' : ''
// report the exception
2019-03-08 11:06:24 -05:00
return this.captureException(attributes, message, `error${note}`)
}
}
},
err() {
2019-03-08 11:06:24 -05:00
return this.error.apply(this, arguments)
},
warn() {
2019-03-08 11:06:24 -05:00
return this.logger.warn.apply(this.logger, arguments)
},
fatal(attributes, message, callback) {
if (callback == null) {
2019-03-08 11:06:24 -05:00
callback = function() {}
}
2019-03-08 11:06:24 -05:00
this.logger.fatal(attributes, message)
if (this.raven != null) {
var cb = function(e) {
// call the callback once after 'logged' or 'error' event
2019-03-08 11:06:24 -05:00
callback()
return (cb = function() {})
}
this.captureException(attributes, message, 'fatal')
this.raven.once('logged', cb)
return this.raven.once('error', cb)
} else {
2019-03-08 11:06:24 -05:00
return callback()
}
}
2019-03-08 11:06:24 -05:00
}
2019-03-08 11:06:24 -05:00
Logger.initialize('default-sharelatex')