Skip to content
/ log Public

Logging utility library with powerful adaptor middleware.

License

Notifications You must be signed in to change notification settings

edge/log

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

96 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Log

Logging utility library with powerful adaptor middleware.

npm version npm downloads license

Usage

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

Basic usage

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')

Full usage

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 })

Log levels

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')

Name

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')

Context

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.

Per message context

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')
}

Per instance context

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')

Merging contexts

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

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

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 a name 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

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)

Logging errors

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]"}}

Using multiple adaptors

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')

Custom adaptors

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')

Contributing

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.

License

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!

About

Logging utility library with powerful adaptor middleware.

Topics

Resources

License

Stars

Watchers

Forks