1.0.3 • Published 11 months ago

events-to-winston v1.0.3

Weekly downloads
-
License
MIT
Repository
github
Last release
11 months ago

workflow Jest coverage

events-to-winston is a module featuring the Tracker class: a tool for observing an arbitrary EventEmitter with a given winston logger.

Each Tracker object listens to a given emitter and transforms incoming events to winston's info objects, which are immediately fed to the specified logger.

One logger can be shared across multiple Tracker instances, but each of them must observe its own, unique emitter.

Tracker is designed to be completely configurable, with 3 the tiered setup (implemented via subclassable-object-merger):

  • unique options set at the instance creation;
  • emitter specific defaults available as its special properties;
  • the hardcoded common default settings.

This way, the Tracker class is presumed to be mostly used as is, without any modifications. While it's always possible to make a subclass, it worth considering to achieve the desired effect by modifying the configuration or by using log formatters.

Installation

npm install events-to-winston

Usage

const {Tracker} = require ('events-to-winston')

// const logger = winston.createLogger (...)
// const myEventEmitter = new MyEventEmitterClass (...)

// myEventEmitter [Tracker.LOGGING_ID] = 1
// myEventEmitter [Tracker.LOGGING_PARENT] = someParentObject // with `Tracker.LOGGING_ID` set

const tracker = new Tracker (emitter, logger, {
//id: `process #${emitter.id}`, 
  events: {
    progress: {
      level: 'info',
//    message: 'progress', // by default, equals to the event name
//    elapsed: true,       // to set `info.elapsed`: ms since the `tracker` creation
  // properties may be computable, `this` is myEventEmitter
//    level:   function (payload) {return this.status == 10 && payload < 50 ? 'notice' : 'info'},
//    message: function (payload) {return `${this.id}: ${payload}%`},
//    details: function (payload) {return {id: this.id, progress: payload}},
    }
  }
//, maxPathLength: 100
//, sep: '/'
})

tracker.listen ()

// myEventEmitter.emit ('progress', 42)

// tracker.unlisten ()

Info objects' properties, tracker settings

As previously stated, for each incoming event mentioned in the configuration, Tracker produces an info object according to winston's conventions. This section describes individual properties of these objects and, at the same time, eponymous tracker's options.

level

This is the only mandatory property in the event's configuration. If set as a string, it's copied into each info object as is. May be set as as function: in this case, it's called with the event's payload as the argument and the underlying event emitter as this.

message

By default, is copied from the event name. Otherwise, is copied as is or evaluated as a function, like level.

details

If configured, must be a plain Object or a function returning plain objects — the latter is called the same way as for level and message.

id

If set, this global tracker option is copied into each info object. It's presumed to be some unique ID of the event emitter being observed.

elapsed

If the elapsed option is set for the event, info.elapsed is the number of milliseconds since the tracker instance was created.

event

This property is always set as the copy of the event's name.

Default configuration

A Tracker instance may be created without any configuration at all: with only emitter and logger parameters. In this case, the getDefaultEvents () result will be used, that is

{
  error: {
    level: 'error', 
    message: error => error.stack, 
    details: error => error
  },
}

If the events option is set explicitly, but lacks any mention of 'error', the default configuration is silently added.

So, at least 'error' events are tracked anyway (which makes sense due to their special properties, like the ability to shut down the entire runtime).

The explicit redefinition, partial or complete, is always available.

emitter's own configuration

The emitter to be observed may not only supply events, but also declare which events are to be logged and how: by exposing the special property [Tracker.LOGGING_EVENTS] (its name is a Symbol available as a Tracker class' static member).

Example:

class MyClass extends EventEmitter {
  get [Tracker.LOGGING_EVENTS] () {
    return {
      start:  {level: 'info'},
      finish: {level: 'info', elapsed: true},
    }
  }
}

So, when actually creating a Tracker instance, the configuration is merged from three sources:

  • the 3rd constructor parameter (if any): highest priority;
  • emitter.[Tracker.LOGGING_EVENTS]: filling gaps;
  • finally the hardcoded default error handling (see the previous section), if left undefined.

id auto discovery

While the tracker's id may be set explicitly as a constructor option, it can also be computed based on the observable emitter. To make it possible, the emitter may publish properties named:

  • [Tracker.LOGGING_ID]: some scalar identifier value;
  • [Tracker.LOGGING_PARENT]: the optional reference to a parent object.

If the id is not set, but emitter [Tracker.LOGGING_ID] is, the Tracker constructor goes through the [Tracker.LOGGING_PARENT] inheritance, constructs the path array, joins it with the sep option ('/' by default) and finally sets as id.

Example:

const service = {}
service [Tracker.LOGGING_ID] = 'mySvc'

const request = new EventEmitter ()
request [Tracker.LOGGING_PARENT] = service
request [Tracker.LOGGING_ID] = '8faa4e0e-d079-4c80-2200-a4a6fc702535'

const tracker = new Tracker (request, logger)
// tracker.id = 'mySvc/8faa4e0e-d079-4c80-2200-a4a6fc702535'

Instead of direct injection, classes using this events-to-winston's feature should set the necessary properties in constructors or define accessor methods like

class MyService {
  get [Tracker.LOGGING_ID] () {
    return this.name
  }
}
class MyServiceRequest {
  get [Tracker.LOGGING_PARENT] () {
    return this.service
  }
  get [Tracker.LOGGING_ID] () {
    return this.uuid
  }
}

details declaration

Along with info.id, info.details is another meta property that may be used to represent some emitter internals to be used in advanced formatters. Think query parameters for HTTP requests or parameter sets for SQL statement calls. And, as for the id field, emitters can publish the special property which content will appear in info.details:

get [Tracker.LOGGING_DETAILS] () {
  return {
    const {parameters} = this
    return {...super [Tracker.LOGGING_DETAILS], parameters}
  }
}

Note that info.details is set only for events with the details option configured, at least as an empty object:

events: {
  start: {
    level: 'info',
    details: {}              // emitter[Tracker.LOGGING_DETAILS] will be used 1st in Object.assign()...
  },
  notice: {
    level: 'info',
    details: ({where}) => ({               
      where,
      parameters: undefined, // ...and may be overridden as needed
    }),   
  },
  finish: {
    level: 'info',
    elapsed: true,           // here, no details at all
  },
}
1.0.3

11 months ago

1.0.2

11 months ago

1.0.1

11 months ago

1.0.0

11 months ago