Logging utility library with powerful adaptor middleware.
The @edge/log
package uses adaptors that act as logging middleware. The most basic adaptor is the standard input/output adaptor StdioAdaptor
which essentially acts like an enhanced console log. Adaptors can be configured and passed in at instantiation, along with the optional parameters name, level, and context, or attached with the use(adaptor)
method.
Install with NPM:
$ npm install @edge/log --save
This example shows basic console logging.
Note, the default log level is Info
so you won't see the debug message:
import { Log, StdioAdaptor } from '@edge/log'
const log = new Log()
log.use(new StdioAdaptor())
log.debug('debugging')
log.info('for your information')
log.warn('achtung! warning!')
log.error('unfortunately, an error occurred')
It is possible to use any number of parameters when instantiating the log:
import { Log, LogLevel, LogtailAdaptor, StdioAdaptor } from '@edge/log'
const name = 'example'
const level = LogLevel.Debug
const context = { timestamp: Date.now() }
const stdioAdaptor = new StdioAdaptor()
const logtailAdaptor = new LogtailAdaptor(process.env.LOGTAIL_SOURCE_TOKEN)
const adaptors = [stdioAdaptor, logtailAdaptor]
const log = new Log(adaptors, name, level, context)
log.info('example', { cool: true })
There are four log levels, exposed via the LogLevel
type.
export enum LogLevel { Debug, Info, Warn, Error }
The default LogLevel
is Info
, but you can set this when creating your Log
instance. All messages equal to and greater than the current log level will be processed, and the others will be ignored.
import { Log, LogLevel, StdioAdaptor } from '@edge/log'
const log = new Log(LogLevel.Warn)
log.use(new StdioAdaptor())
log.debug('you won\'t see me')
log.info('or me')
log.warn('but you will see me')
log.error('and me')
You can change the log level at runtime by using the setLogLevel(level)
method:
import { Log, LogLevel, StdioAdaptor } from '@edge/log'
const log = new Log(LogLevel.Warn)
log.use(new StdioAdaptor())
log.debug('you won\'t see me')
log.setLogLevel(LogLevel.Info)
log.info('but you will see me now')
You can assign a name to Log
instances, for example:
const log = new Log('readme')
log.use(new StdioAdaptor())
log.info('this is an example')
You can extend the name with the extend
method:
const log = new Log('readme')
log.use(new StdioAdaptor())
log.info('this is an example')
const eventLog = log.extend('event')
eventLog.info('the name of this log will be readme:event')
You can pass in context along with your log message. In the case of StdioAdaptor
, this is stringified and output. For LogtailAdaptor
, it is passed along to Logtail. How it is utilised in down to the adaptors. You can also attach context to a Log
instance itself, therefore including it with every message.
For example, you could attach debug data to a message:
const log = new Log([new StdioAdaptor()], LogLevel.Debug)
log.debug('debug context example', { debugData: [1, 2, 3] })
Or perhaps an error:
try {
// imagine something bad happens here
throw new Error('something bad happened')
}
catch (err: any) {
if (err instanceof Error) log.error('an error was caught', err)
else log.error('an unknown error was caught')
}
Context can also be attached to the Log
instance itself, so that it is included with every message. This can be done in one of two ways.
At instantiation:
import { Log, StdioAdaptor } from '@edge/log'
const log = new Log({
instance: generateInstanceID(),
someFlag: true
})
log.use(new StdioAdaptor())
log.info('example')
Or by extending an existing Log
instance:
import { Log, StdioAdaptor } from '@edge/log'
const log = new Log([new StdioAdaptor()])
const event = { eventID: 528 }
const eventLog = log.extend(event)
eventLog.info('event started')
Contexts are automatically merged. This means you can extend a Log
instance with some context, then add to it within a message, and also extend it with further context.
import { Log, StdioAdaptor } from '@edge/log'
const log = new Log({
instance: generateInstanceID(),
someFlag: true
})
log.use(new StdioAdaptor())
const eventLog = log.extend({ eventName: 'testEvent' })
eventLog.info('event started', { eventStartDate: new Date() })
The above example would start with the log
instance and the context:
{
instance: 'ed5eb32b',
someFlag: true
}
Then the eventLog
instance would have the context:
{
instance: 'ed5eb32b',
someFlag: true,
eventName: 'testEvent'
}
And the info message would have the context:
{
instance: 'ed5eb32b',
someFlag: true,
eventName: 'testEvent',
eventStartDate: '2021-10-04T20:16:49.988Z'
}
Adaptors form the powerful middleware layer of @edge/log
. At their most basic, they are objects with four methods: debug
, info
, warn
, and error
, and each of these methods take three parameters: the Log
instance, a log message as a string, and an optional context object. See custom adaptors below for more details.
LogtailAdaptor
pushes your log messages to Logtail. For this, you'll need an API key, or what Logtail call a Source Token.
It takes two parameters, logtailSourceToken
and an optional enableNameInjection
(default: true
).
Usage:
const log = new Log()
const logtail = new LogtailAdaptor(process.env.LOGTAIL_SOURCE_TOKEN)
log.use(logtail)
Note: if you set a Name on the
Log
instance, the Logtail adaptor will inject this into the context that it uploads. Avoid setting aname
field on the root of the context object or disable name injection like so:
const log = new Log()
const logtail = new LogtailAdaptor(process.env.LOGTAIL_SOURCE_TOKEN, false)
log.use(logtail)
StdioAdaptor
is a simple adaptor that outputs logs to stdout (and if you configure it, stderr).
It takes one parameter, useStderr
. If this is set, errors will be written to stderr instead of stdout.
// Errors will be written to stdout
const stdoutOnly = new StdioAdaptor()
// Errors will be written to stderr
const stderrToo = new StdioAdaptor(true)
StdioAdaptor
handles error logging differently based on how the error is supplied.
If the error is provided as the message (first argument) or context (second argument) then it will be presented as a stack in log output. For example:
log.error(new Error("some error"))
// 14:34:12.992 ERR Error: some error
// Error: some error
// at Object.<anonymous> (***/tests/index.ts:30:4)
// [...etc]
log.error("my msg", new Error("some error"))
// 14:34:12.992 ERR my msg
// Error: some error
// at Object.<anonymous> (***/tests/index.ts:30:4)
// [...etc]
However, if an error is provided as a property of the context, it will be presented as an ordinary object:
const err = new Error("some error")
log.error("my msg", { err })
// 14:38:08.031 ERR my msg {"err":{"message":"some error","name":"Error","stack":"Error: some error\n at Object.<anonymous> (***/tests/index.ts:30:4) [...etc]"}}
You can easily use multiple adaptors. You can either pass them in at instantiation:
import { Log, LogtailAdaptor, StdioAdaptor } from '@edge/log'
const log = new Log([
new StdioAdaptor(),
new LogtailAdaptor(process.env.LOGTAIL_SOURCE_TOKEN)
])
log.debug('debugging')
Or attach them with the use
method:
import { Log, LogtailAdaptor, StdioAdaptor } from '@edge/log'
const log = new Log()
log.use(new StdioAdaptor())
log.use(new LogtailAdaptor(process.env.LOGTAIL_SOURCE_TOKEN))
log.debug('debugging')
It is possible, encouraged even, to write and use custom adaptors. Adaptors must conform to the following type, which simply has four methods.
export type Adaptor = {
debug: (log: Log, message: string, context?: Record<string, unknown>) => void
info: (log: Log, message: string, context?: Record<string, unknown>) => void
warn: (log: Log, message: string, context?: Record<string, unknown>) => void
error: (log: Log, message: string, context?: Record<string, unknown>) => void
}
For example
import { Adaptor, Log } from '@edge/log'
class ConsoleLogAdaptor implements Adaptor {
debug(log: Log, message: string, context?: Record<string, unknown>): void {
console.log('DEBUG', message, log.name, context)
}
info(log: Log, message: string, context?: Record<string, unknown>): void {
console.log('INFO', message, log.name, context)
}
warn(log: Log, message: string, context?: Record<string, unknown>): void {
console.log('WARN', message, log.name, context)
}
error(log: Log, message: string, context?: Record<string, unknown>): void {
console.log('ERROR', message, log.name, context)
}
}
const log = new Log('custom log example')
log.use(new ConsoleLogAdaptor())
log.info('hello from the console')
Interested in contributing to the project? Amazing! Before you do, please have a quick look at our Contributor Guidelines where we've got a few tips to help you get started.
Edge is the infrastructure of Web3. A peer-to-peer network and blockchain providing high performance decentralised web services, powered by the spare capacity all around us.
Copyright notice
(C) 2021 Edge Network Technologies Limited [email protected]
All rights reserved
This product is part of Edge. Edge is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version ("the GPL").
If you wish to use Edge outside the scope of the GPL, please contact us at [email protected] for details of alternative license arrangements.
This product may be distributed alongside other components available under different licenses (which may not be GPL). See those components themselves, or the documentation accompanying them, to determine what licenses are applicable.
Edge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
The GNU General Public License (GPL) is available at: https://www.gnu.org/licenses/gpl-3.0.en.html
A copy can be found in the file GPL.md distributed with
these files.
This copyright notice MUST APPEAR in all copies of the product!