Skip to content

Instantly share code, notes, and snippets.

@akidee
Created December 16, 2011 22:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akidee/1488297 to your computer and use it in GitHub Desktop.
Save akidee/1488297 to your computer and use it in GitHub Desktop.
An example for a JS logger API, nearly beta
_ = require('underscorex')
step = require('stepc')
slice = Array::slice
###
Constructor for loggers. A logger is a log function with properties that make it configurable. Pass:
preTraits: one or several (Array) traits
traits: "
transports: one or several transport instances
###
Logger = exports.Logger = (options = {}) ->
_data = _send = undefined
logger = ->
__ = arguments[arguments.length - 1]
if typeof __ != 'function'
_send(_data.apply(null, arguments))
# Does a callback exist?
else
args = slice.call(arguments, 0, arguments.length - 1)
_send(_data.apply(null, args), __)
(->
_data = () =>
traitData = []
for trait in @preTraits
traitData.push(trait())
for arg, i in arguments
trait = @traits[i]
data = if !trait
arg
else
trait(arg)
traitData.push(data)
traitData
_send = (data, __) =>
if !__
for transport in @transports
transport.send(data.reduce(transport.reduceCallback, transport.reduceGetInit()))
else
if !@transports.length
return __()
_this = @
step.async(
(e, next) ->
group = @group()
for transport in _this.transports
serializedData = data.reduce(transport.reduceCallback, transport.reduceGetInit())
if transport.send.length <= 1
# try catch?
transport.send(serializedData)
group()()
else
transport.send(serializedData, group())
(e, results) ->
if e
return __(e)
__(null, results)
)
@traits = if !(options.traits instanceof Array)
traits = []
if options.traits
traits.push(options.traits)
traits
else
options.traits
@preTraits = if !(options.preTraits instanceof Array)
preTraits = []
if options.preTraits
preTraits.push(options.preTraits)
preTraits
else
options.preTraits
@transports = if !(options.transports instanceof Array)
transports = []
if options.transports
transports.push(options.transports)
transports
else
options.transports
@
).call(logger)
###
Create several logging levels. Every object key points to a logger
###
create = exports.create = (levels, defaultOptions = {}) ->
c = {}
for own level, _options of levels
options = _.extend({}, defaultOptions, _options)
c[level] = new Logger(options)
c
###
Traits are default aspects implemented as functions. They are either called without argument (as preTraits) or with exactly 1 argument (as traits which serve as a map function).
Every logger can have several preTraits and traits. preTraits are always called - without any argument. If there are traits, they are called, one by one, argument by argument, and can filter a large object to return only the properties that should be logged. Take a look at ./traits/node.coffee to see some map functions for node.js standard instances.
If there are no traits the arguments are taken as they are. All objects are pushed to a data object, whcih represents the unserialized essence of this log call.
###
traits = exports.traits = {}
_.extend(
traits
require('./traits/common')
require('./traits/node')
)
###
After traits have been applied, the data must be serialized.
Example: Serialize data in a readable way to a string and send it to stdout, or convert the data array into an object and insert it into a MongoDB collection.
###
transports = exports.transports =
MongoDB: require('./transports/mongoDB')
WritableStream: require('./transports/writableStream')
###
The default console object can be implemented by using traits and transports.
This helps to change the default behavior of a function as console.log. For example, you can add an additional transport to persist a log, simply add preTraits, like logging memory usage or time.
###
exports.createConsole = ->
c =
log:
transports: transports.WritableStream.stdout
warn:
transports: transports.WritableStream.stderr
dir:
transports: new transports.WritableStream(process.stdout, on)
c.info = c.log
c.error = c.warn
create(c)
exports.createPragmaticLoggers = (persistentTransport = null) ->
###
Only info, warn, error are rarely used or in an exceptional case, so they log to stdout/err
by default.
###
c =
log: # frequently called
preTraits: traits.t
info: # called regularly but not frequently, for basic info
preTraits: traits.t
transports: transports.WritableStream.stdout
warn: # not dangerous, but it should be go to both the console and persistence layer
preTraits: traits.t
transports: transports.WritableStream.stderr
error: # same here
preTraits: traits.t
traits: traits.error
transports: transports.WritableStream.stderr
serverRequest: # frequent
preTraits: traits.t
traits: traits.serverRequest
call: # frequent
preTraits: traits.t
traits: [ traits.call_name, traits.call_args ]
process: # regularly
preTraits: [ traits.t, traits.process ]
memoryUsage: # regularly
preTraits: [ traits.t, traits.memoryUsage ]
loadavg: # regularly
preTraits: [ traits.t, traits.loadavg ]
c = create(c)
# All functions, except for info, send to the given persistent transport
if persistentTransport
[ 'log', 'warn', 'error', 'serverRequest', 'call', 'process', 'memoryUsage', 'loadavg' ]
.forEach((level) ->
c[level].transports.push(persistentTransport)
)
c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment