Scribbly is a simple isomorphic logging tool which is based on middleware system. Management and construction of middlewares are very similar to the ones in expressjs. This allows broad flexibility and it keeps api simple.
Installation:
npm install scribbly --save
Simple logging to the console
import scribbly from 'scribbly' // const scribbly = require('scribbly').default
import { consoleStreamer } from 'scribbly/middlewares'
const log = scribbly.use(consoleStreamer)
log.debug('Hello')
log.info('Hello')
log.warning('Hello')
log.error('Hello')
log.critical('Hello')
Using namespaces
export DEBUG=n1,n3 # or window.DEBUG=n1,n3 in the browser
import scribbly from 'scribbly'
import { consoleStreamer, namespace } from 'scribbly/middlewares'
const n1 = scribbly.use(namespace('n1')).use(consoleStreamer)
const n2 = scribbly.use(namespace('n2')).use(consoleStreamer)
n1.info('Hello from n1')
n2.info('Hello from n2')
Output:
[n1] Hello from n1
Adjusting logging strategy
It's common to log production errors to error reporting services like Rollbar but for development log them to console. We can easily apply different middlewares on different conditions to make that possible.
logger.js
import scribbly from 'scribbly'
import rollbar from 'rollbar-browser'
import { consoleStreamer, externalLogger } from 'scribbly/middleware'
let logger
if (process.env.NODE_ENV === 'production') {
let rollbarLogger = rollbar.init(someConfig)
logger = scribbly.use(externalLogger(rollbarLogger))
} else {
logger = scribbly.use(consoleStreamer)
}
export default logger
import log from './logger'
log.error('Some error')
Keep in mind that without any middleware scribbly actually won't do anything and without
streaming middleware like consoleStreamer
logs won't be emitted or written.
For each logging, scribbly goes through a chain of middlewares in the same order how
they were defined by use
method. Middlewares are just pure functions which can
modify the message (formatter), emit it to the console/file (streamer) or prevent it from
further emission to the rest of the chain (filter). Having that said they can actually do
anything, they are just functions.
Construction
const log = scribbly.use((next, level, message, extras) => {
next(message, extras)
})
next
- {Function} calls the next middleware from the chain. If not called it breaks the chain.level
- {Number} level of the logmessage
- main message, can be a string or any other typeextras
- optional extra data, can be any type
Immutability
Every time when use
method is called it returns a new logger object with freshly defined
middleware and the middlewares from the previous logger. This have nice implications
when in one module you can store the main logger, import it to other modules and
add new middlewares there if needed without any modifications to the original
logger.
Order
We can distinguish 3 types of middleware: filter, formatter and streamer. Order in which those are applied is very important. If streamer will be added earlier than filter or formatter it means that those 2 will have no effect on the log emission through the streamer. Unless it is intended it is a good practice to add streamers as the last ones to the middlewares chain.
import { ... } from 'scribbly/middlewares'
Emit log to the output using console.log
, console.warn
or console.error
depend on log
level.
scribbly.use(consoleStreamer)
Passes logs only when isOn
is true
. Useful when we want to disable/enable logs to
a certain condition. Should be applied as first.
scribbly.use(enableWhen(process.env.DEBUG))
Logs to a given logger. It is expected that external logger should provide methods: debug, info, warning, error, critical. If it doesn't it should be wrapped around that kind of interface before.
scribbly.use(externalLogger(rollbarBrowserLogger))
Only for node. Logs to a given file. If file does not exist it creates it.
import fs from 'fs'
scribbly.use(fileStreamer(fs, './logs.txt'))
Passes only logs which are equal or higher than given level.
import scribbly, { levels } from 'scribbly'
scribbly.use(levelFilter(levels.ERROR))
Passes logs only if given namespace is found within DEBUG
global. DEBUG
global
should be a string which represents list of namespaces seperated by comma. Wildcards
are respected. The middleware also adds namespace name to the log message as a prefix.
It works in a isomorphic nature and it uses different DEBUG
global on different
environments (browser/node):
export DEBUG=n1,n2:sub:* // in the terminal when node (access through process.env.DEBUG)
window.DEBUG=n1,n2:sub:* // in the browser when client
const log = scribbly.use(namespace('n1')).use(consoleStreamer)
log.info('test')
Output:
[n1] test
To pass logs from all namespaces:
DEBUG=*
It adds time information to the message.
scribbly.use(timeFormatter)
import { Logger } from 'scribbly'
Main class of the scribbly logger. Instance of it can by imported by
import scribbly from 'scribbly'
. It's the same as const scribbly = new Logger()
.
constructor(middlewares = [])
Creates logger and set an Array of middlewares.
middlewares
Property. Array of middlewares. Can't be modified, it's frozen.
use(middleware)
Returns a new logger with combined old middlewares and the new one.
return new Logger(this.middlewares.concat(middleware))
log(level, message, extras)
Emit the message with extras through middlewares, in order.
level
- {Number} level of the logmessage
- Any type, main content of the logextras
- Any type, optional data
debug(message, extras)
Same as log(levels.DEBUG, message, extras)
.
info(message, extras)
Same as log(levels.INFO, message, extras)
.
warning(message, extras)
Same as log(levels.WARNING, message, extras)
.
error(message, extras)
Same as log(levels.ERROR, message, extras)
.
critical(message, extras)
Same as log(levels.CRITICAL, message, extras)
.
import { levels } from 'scribly'
Stores a set of constants which stores numerical representation of logging levels.
export const levels = {
DEBUG: 10,
INFO: 20,
WARNING: 30,
ERROR: 40,
CRITICAL: 50
}