overleaf/libraries/logger/logging-manager.js

152 lines
4 KiB
JavaScript
Raw Normal View History

const Stream = require('stream')
2019-03-08 11:06:24 -05:00
const bunyan = require('bunyan')
const GCPManager = require('./gcp-manager')
const SentryManager = require('./sentry-manager')
const Serializers = require('./serializers')
const {
FileLogLevelChecker,
GCEMetadataLogLevelChecker,
} = require('./log-level-checker')
const LoggingManager = {
initialize(name) {
this.isProduction =
2020-07-02 05:51:37 -04:00
(process.env.NODE_ENV || '').toLowerCase() === 'production'
this.defaultLevel =
process.env.LOG_LEVEL || (this.isProduction ? 'info' : 'debug')
2019-03-08 11:06:24 -05:00
this.loggerName = name
this.logger = bunyan.createLogger({
name,
serializers: {
err: Serializers.err,
req: Serializers.req,
res: Serializers.res,
},
streams: [this._getOutputStreamConfig()],
2019-03-08 11:06:24 -05:00
})
this._setupRingBuffer()
this._setupLogLevelChecker()
2019-03-08 11:06:24 -05:00
return this
},
initializeErrorReporting(dsn, options) {
this.sentryManager = new SentryManager()
},
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)
},
error(attributes, message, ...args) {
2019-03-12 08:23:37 -04:00
if (this.ringBuffer !== null && Array.isArray(this.ringBuffer.records)) {
2020-07-02 06:01:23 -04:00
attributes.logBuffer = this.ringBuffer.records.filter(function (record) {
2019-03-12 08:23:37 -04:00
return record.level !== 50
})
}
2019-03-08 11:06:24 -05:00
this.logger.error(attributes, message, ...Array.from(args))
if (this.sentryManager) {
this.sentryManager.captureExceptionRateLimited(attributes, message)
}
},
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) {
2019-03-08 11:06:24 -05:00
this.logger.fatal(attributes, message)
if (this.sentryManager) {
this.sentryManager.captureException(attributes, message, 'fatal')
}
},
_getOutputStreamConfig() {
switch (process.env.LOGGING_FORMAT) {
case 'gke': {
const stream = new Stream.Writable({
objectMode: true,
write(entry, encoding, callback) {
const gcpEntry = GCPManager.convertLogEntry(entry)
// eslint-disable-next-line no-console
console.log(JSON.stringify(gcpEntry, bunyan.safeCycles()))
setImmediate(callback)
},
})
return { level: this.defaultLevel, type: 'raw', stream }
}
case 'gce': {
const { LoggingBunyan } = require('@google-cloud/logging-bunyan')
return new LoggingBunyan({
logName: this.loggerName,
serviceContext: { service: this.loggerName },
}).stream(this.defaultLevel)
}
default: {
return { level: this.defaultLevel, stream: process.stdout }
}
}
},
_setupRingBuffer() {
2020-07-02 05:51:37 -04:00
this.ringBufferSize = parseInt(process.env.LOG_RING_BUFFER_SIZE) || 0
if (this.ringBufferSize > 0) {
this.ringBuffer = new bunyan.RingBuffer({ limit: this.ringBufferSize })
this.logger.addStream({
level: 'trace',
type: 'raw',
stream: this.ringBuffer,
})
} else {
this.ringBuffer = null
}
},
_setupLogLevelChecker() {
const logLevelSource = (
process.env.LOG_LEVEL_SOURCE || 'file'
).toLowerCase()
if (this.logLevelChecker) {
this.logLevelChecker.stop()
this.logLevelChecker = null
}
if (this.isProduction) {
switch (logLevelSource) {
case 'file':
this.logLevelChecker = new FileLogLevelChecker(
this.logger,
this.defaultLevel
)
break
case 'gce_metadata':
this.logLevelChecker = new GCEMetadataLogLevelChecker(
this.logger,
this.defaultLevel
)
break
case 'none':
break
default:
// eslint-disable-next-line no-console
console.log(`Unrecognised log level source: ${logLevelSource}`)
}
if (this.logLevelChecker) {
this.logLevelChecker.start()
}
}
},
}
LoggingManager.initialize('default-sharelatex')
module.exports = LoggingManager