Handles server logging using local files, Logentries or Loggly. Multiple services can be enabled at the same time. Parameters on settings.html: Settings.Logger
class Logger
fs = require "fs"
lodash = require "lodash"
moment = require "moment"
path = require "path"
settings = require "./settings.coffee"
utils = require "./utils.coffee"
Local logging objects will be set on init
.
bufferDispatcher = null
localBuffer = null
Remote logging providers will be set on init
.
logentries = null
loggly = null
loggerLogentries = null
loggerLoggly = null
The serverIP
will be set on init, but only if settings.logger.sendIP
is true.
serverIP = null
Timer used for automatic logs cleaning.
timerCleanLocal = null
Holds a list of current active logging services.
activeServices = []
Init the Logger. Verify which services are set, and add the necessary transports. IP address and timestamp will be appended to logs depending on the settings.
init: =>
if bufferDispatcher?
@flushLocal()
clearInterval bufferDispatcher
bufferDispatcher = null
localBuffer = null
logentries = null
loggly = null
serverIP = null
activeServices = []
Get a valid server IP to be appended to logs.
if settings.logger.sendIP
serverIP = utils.getServerIP()
Check if logs should be saved locally. If so, create the logs buffer and a timer to flush logs to disk every X milliseconds.
if settings.logger.local.enabled
if fs.existsSync?
folderExists = fs.existsSync settings.path.logsDir
else
folderExists = path.existsSync settings.path.logsDir
Create logs folder, if it doesn't exist.
if not folderExists
fs.mkdirSync settings.path.logsDir
Set local buffer.
localBuffer = {info: [], warn: [], error: []}
bufferDispatcher = setInterval @flushLocal, settings.logger.local.bufferInterval
activeServices.push "Local"
Check the maxAge of local logs.
if settings.logger.local.maxAge? and settings.logger.local.maxAge > 0
if timerCleanLocal?
clearInterval timerCleanLocal
timerCleanLocal = setInterval @cleanLocal, 86400
Check if Logentries should be used, and create the Logentries objects.
if settings.logger.logentries.enabled and settings.logger.logentries.token? and settings.logger.logentries.token isnt ""
logentries = require "node-logentries"
loggerLogentries = logentries.logger {token: settings.logger.logentries.token, timestamp: settings.logger.sendTimestamp}
activeServices.push "Logentries"
Check if Loggly should be used, and create the Loggly objects.
if settings.logger.loggly.enabled and settings.logger.loggly.subdomain? and settings.logger.loggly.token? and settings.logger.loggly.token isnt ""
loggly = require "loggly"
loggerLoggly = loggly.createClient {subdomain: settings.logger.loggly.subdomain, json: true}
activeServices.push "Loggly"
Define server IP.
if serverIP?
ipInfo = "IP #{serverIP}"
else
ipInfo = "No server IP set."
Check if uncaught exceptions should be logged. If so, try logging unhandled exceptions using the logger, otherwise log to the console.
if settings.logger.uncaughtException
process.on "uncaughtException", (err) ->
try
@error "Expresser", "Unhandled exception!", err.stack
catch ex
console.error "Expresser", "Unhandled exception!", Date(Date.now()), err.stack, ex
Start logging!
if not localBuffer? and not logentries? and not loggly?
@warn "Expresser", "Logger.init", "No transports enabled.", "Logger module will only log to the console!"
else
@info "Expresser", "Logger.init", activeServices.join(), ipInfo
Log any object to the default transports as log
.
info: =>
console.info.apply(this, arguments) if settings.general.debug or activeServices.length < 1
msg = @getMessage arguments
if localBuffer?
@logLocal "info", msg
if logentries?
loggerLogentries.info.apply this, [msg]
if loggly?
loggerLoggly.log.apply this, [settings.logger.loggly.token, msg]
Log any object to the default transports as warn
.
warn: =>
console.warn.apply(this, arguments) if settings.general.debug or activeServices.length < 1
msg = @getMessage arguments
if localBuffer?
@logLocal "warn", msg
if logentries?
loggerLogentries.warning.apply this, [msg]
if loggly?
loggerLoggly.log.apply this, [settings.logger.loggly.token, msg]
Log any object to the default transports as error
.
error: =>
console.error.apply(this, arguments) if settings.general.debug or activeServices.length < 1
msg = @getMessage arguments
if localBuffer?
@logLocal "error", msg
if logentries?
loggerLogentries.err.apply this, [msg]
if loggly?
loggerLoggly.log.apply this, [settings.logger.loggly.token, msg]
Log locally. The path is defined on Settings.Path.logsDir
.
logLocal: (logType, message) ->
now = moment()
message = now.format("HH:mm:ss.SSS") + " - " + message
localBuffer[logType] = [] if not localBuffer[logType]?
localBuffer[logType].push message
Flush all local buffered log messages to disk. This is usually called by the bufferDispatcher
timer.
flushLocal: ->
now = moment()
date = now.format "YYYYMMDD"
Flush all buffered logs to disk. Please note that messages from the last seconds of the previous day can be saved to the current day depending on how long it takes for the bufferDispatcher to run. Default is every 10 seconds, so messages from 23:59:50 onwards could be saved on the next day.
for key, logs of localBuffer
if logs.length > 0
writeData = logs.join("\n")
localBuffer[key] = []
filePath = path.join settings.path.logsDir, "#{date}.#{key}.log"
fs.appendFile filePath, writeData, (err) -> console.error("Expresser", "Logger.flushLocal", err) if err?
Delete old log files.
cleanLocal: ->
maxDate = moment().subtract "d", settings.logger.local.maxAge
fs.readdir settings.path.logsDir, (err, files) ->
if err?
console.error "Expresser", "Logger.cleanLocal", err
else
for f in files
date = moment f.split(".")[1], "yyyyMMdd"
if date.isBefore maxDate
fs.unlink path.join(settings.path.logsDir, f), (err) ->
console.error("Expresser", "Logger.cleanLocal", err) if err?
Serializes the parameters and return a JSON object representing the log message, depending on the service being used.
getMessage: ->
separated = []
args = arguments
args = args[0] if args.length is 1
Parse all arguments and stringify objects. Please note that fields defined
on the Settings.logger.removeFields
won't be added to the message.
for a in args
if settings.logger.removeFields.indexOf(a) < 0
if lodash.isArray a
for b in a
separated.push b if settings.logger.removeFields.indexOf(b) < 0
else if lodash.isObject a
try
separated.push JSON.stringify a
catch ex
separated.push a
else
separated.push a
Append IP address, if serverIP
is set.
separated.push "IP #{serverIP}" if serverIP?
Return single string log message.
return separated.join " | "
Logger.getInstance = ->
@instance = new Logger() if not @instance?
return @instance
module.exports = exports = Logger.getInstance()