Logging system with decoupled producers/consumer
npm install @xen-orchestra/log !License  
> Logging system with decoupled producers/consumer
Installation of the npm package:
``sh`
npm install --save @xen-orchestra/log
Everywhere something should be logged:
`js
import { createLogger } from '@xen-orchestra/log'
const log = createLogger('my-module')
log.debug('only useful for debugging')
log.info('this information is relevant to the user')
log.warn('something went wrong but did not prevent current action')
log.error('something went wrong')
log.fatal('service/app is going down')
// you can add contextual info
log.debug('new API request', {
method: 'foo',
params: [ 'bar', 'baz' ]
user: 'qux'
})
// by convention, errors go into the error field`
log.error('could not join server', {
error,
server: 'example.org',
})
A logging method has the following signature:
`ts
interface LoggingMethod {
(error): void
(message: string, data?: { error?: Error; [property: string]: any }): void
}
`
Then, at application level, configure the logs are handled:
`js
import { createLogger } from '@xen-orchestra/log'
import { configure, catchGlobalErrors } from '@xen-orchestra/log/configure'
import transportConsole from '@xen-orchestra/log/transports/console'
import transportEmail from '@xen-orchestra/log/transports/email'
const transport = transportEmail({
service: 'gmail',
auth: {
user: 'jane.smith@gmail.com',
pass: 'H&NbECcpXF|pyXe#%ZEb',
},
from: 'jane.smith@gmail.com',
to: ['jane.smith@gmail.com', 'sam.doe@yahoo.com'],
})
configure([
{
filter: process.env.DEBUG,
transport: transportConsole(),
},
{
// only levels >= warn
level: 'warn',
transport,
},
{
type: 'email',
service: 'gmail',
auth: {
user: 'jane.smith@gmail.com',
pass: 'H&NbECcpXF|pyXe#%ZEb',
},
from: 'jane.smith@gmail.com',
to: ['jane.smith@gmail.com', 'sam.doe@yahoo.com'],
},
])
// send all global errors (uncaught exceptions, warnings, unhandled rejections)
// to this logger
catchGlobalErrors(createLogger('app'))
`
A transport as expected by configure(transport) can be:
- a function that will receive emitted logs;
- an object with a type property and options which will be used to create a transport (see next section);transport
- an object with a nested which will be used if one of the following conditions is fulfilled:filter
- : pattern which is matched against the log namespace (can also be an array of filters);level
- : the minimal level of accepted logs;
- an array of transports.
#### Console
`js
import transportConsole from '@xen-orchestra/log/transports/console'
configure(transportConsole())
`
Optional dependency:
``
> yarn add nodemailer pretty-format
Configuration:
`js
import transportEmail from '@xen-orchestra/log/transports/email'
configure(
transportEmail({
service: 'gmail',
auth: {
user: 'jane.smith@gmail.com',
pass: 'H&NbECcpXF|pyXe#%ZEb',
},
from: 'jane.smith@gmail.com',
to: ['jane.smith@gmail.com', 'sam.doe@yahoo.com'],
})
)
`
#### Syslog
Optional dependency:
``
> yarn add split-host syslog-client
Configuration:
`js
import transportSyslog from '@xen-orchestra/log/transports/syslog'
// By default, log to udp://localhost:514
configure(transportSyslog())
// But TCP, a different host, or a different port can be used
configure(transportSyslog({ target: 'tcp://syslog.company.lan' }))
`
#### Dedupe
> Wraps a transport to limit the number of duplicate logs.
`js
import { dedupe } from '@xen-orchestra/log/dedupe'
configure(
dedupe({
timeout: 500e3, // default to 600e3, ie 10 minutes
transport: console.log,
})
)
`
Duplicate logs will be buffered for timeout milliseconds or until a different log is emitted, at which time a dedicated log entry is emitted which indicate the number of duplicates that occured.
`js
const logger = createLogger('app')
// Log some duplicate messages
logger.error('Something went wrong')
logger.error('Something went wrong')
logger.error('Something went wrong')
// Log a different message
logger.info('This is a different message')
`
In this example, the first three log entries are identical and the last two will be treated as duplicates. They will be grouped together and sent to the transport as a single log with nDuplicates: 2 data when a different log entry is emitted (or after the timeout as elasped if there weren't one).
The output in the console would look something like:
``
app ERROR Something went wrong
app ERROR duplicates of the previous log were hidden { nDuplicates: 2 }
app INFO This is a different message
#### Capture
> Allow capturing all logs emitted during a call, even through asynchronous operations.
Before being able to use this feature, you need to add the transport:
`js
import { configure } from '@xen-orchestra/log/configure'
import { createCaptureTransport } from '@xen-orchestra/log/capture'
import createConsoleTransport from '@xen-orchestra/log/transports/console'
// transport that will be used globally, when not in a captured environment
const fallbackTransport = {
filter: process.env.DEBUG,
level: 'warn',
transport: createConsoleTransport(),
}
// create the capture transport and pass it the fallback one
const captureTransport = createCaptureTransport(fallbackTransport)
// configure @xen-orchestra/log to use our transport
configure(captureTransport)
`
Now the captureLogs(onLog, fn) can be used:
`js
import { captureLogs } from '@xen-orchestra/log/capture'
import { createLogger } from '@xen-orchestra/log'
const logger = createLogger('my-logger')
await captureLogs(
(log, fallbackTransport) => {
// every logs emitted in the async context of fn will arrive here
//
// do not emit logs in this function or this will create a loop.
// logs can be forwarded to the fallback transport
fallbackTransport(log)
},
async () => {
logger.debug('synchronous logs are captured')
setTimeout(() => {
logger.debug('logs from asynchronous callbacks too')
}, 50)
await new Promise(resolve => setTimeout(resolve, 50))
logger.debug('logs in async functions or promise chains too')
// To escape capture, run code in captureLogs with undefined
// as the first param
captureLogs(undefined, () => {
logger.debug('this log will not be captured')
})
// Returned value and error is forwarded by captureLogs``
return Math.PI
}
)
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any issue
you've encountered;
- fork and create a pull request.