npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

events-to-winston

v1.0.3

Published

Observing an EventEmitter, logging to winston

Downloads

157

Readme

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