| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- bunyan = require('bunyan')
- request = require('request')
- module.exports = Logger =
- initialize: (name) ->
- isProduction = process.env['NODE_ENV']?.toLowerCase() == 'production'
- @defaultLevel = process.env['LOG_LEVEL'] or if isProduction then "warn" else "debug"
- @loggerName = name
- @logger = bunyan.createLogger
- name: name
- serializers: bunyan.stdSerializers
- level: @defaultLevel
- if isProduction
- # check for log level override on startup
- @.checkLogLevel()
- # re-check log level every minute
- checkLogLevel = () => @.checkLogLevel()
- setInterval(checkLogLevel, 1000 * 60)
- return @
- checkLogLevel: () ->
- options =
- headers:
- "Metadata-Flavor": "Google"
- uri: "http://metadata.google.internal/computeMetadata/v1/project/attributes/#{@loggerName}-setLogLevelEndTime"
- request options, (err, response, body) =>
- if parseInt(body) > Date.now()
- @logger.level('trace')
- else
- @logger.level(@defaultLevel)
- initializeErrorReporting: (sentry_dsn, options) ->
- raven = require "raven"
- @raven = new raven.Client(sentry_dsn, options)
- @lastErrorTimeStamp = 0 # for rate limiting on sentry reporting
- @lastErrorCount = 0
- captureException: (attributes, message, level) ->
- # handle case of logger.error "message"
- if typeof attributes is 'string'
- attributes = {err: new Error(attributes)}
- # extract any error object
- error = attributes.err or attributes.error
- # avoid reporting errors twice
- for key, value of attributes
- return if value instanceof Error && value.reportedToSentry
- # include our log message in the error report
- if not error?
- error = {message: message} if typeof message is 'string'
- else if message?
- attributes.description = message
- # report the error
- if error?
- # capture attributes and use *_id objects as tags
- tags = {}
- extra = {}
- for key, value of attributes
- tags[key] = value if key.match(/_id/) and typeof value == 'string'
- extra[key] = value
- # capture req object if available
- req = attributes.req
- if req?
- 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) and typeof error is "object"
- newError = new Error(error.message)
- for own key, value of error
- 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
- error.message = error.message.replace(" '#{error.path}'", '') if error.path
- # send the error to sentry
- try
- @raven.captureException(error, {tags: tags, extra: extra, level: level})
- # put a flag on the errors to avoid reporting them multiple times
- for key, value of attributes
- value.reportedToSentry = true if value instanceof Error
- catch
- return # ignore any errors
- debug : () ->
- @logger.debug.apply(@logger, arguments)
- info : ()->
- @logger.info.apply(@logger, arguments)
- log : ()->
- @logger.info.apply(@logger, arguments)
- error: (attributes, message, args...)->
- @logger.error(attributes, message, args...)
- if @raven?
- MAX_ERRORS = 5 # maximum number of errors in 1 minute
- now = new Date()
- # have we recently reported an error?
- recentSentryReport = (now - @lastErrorTimeStamp) < 60 * 1000
- # if so, increment the error count
- if recentSentryReport
- @lastErrorCount++
- else
- @lastErrorCount = 0
- @lastErrorTimeStamp = now
- # only report 5 errors every minute to avoid overload
- if @lastErrorCount < MAX_ERRORS
- # add a note if the rate limit has been hit
- note = if @lastErrorCount+1 is MAX_ERRORS then "(rate limited)" else ""
- # report the exception
- @captureException(attributes, message, "error#{note}")
- err: () ->
- @error.apply(this, arguments)
- warn: ()->
- @logger.warn.apply(@logger, arguments)
- fatal: (attributes, message, callback = () ->) ->
- @logger.fatal(attributes, message)
- if @raven?
- cb = (e) -> # call the callback once after 'logged' or 'error' event
- callback()
- cb = () ->
- @captureException(attributes, message, "fatal")
- @raven.once 'logged', cb
- @raven.once 'error', cb
- else
- callback()
- Logger.initialize("default-sharelatex")
|