{"id":22356332,"url":"https://github.com/do-/node-events-to-winston","last_synced_at":"2026-01-22T12:02:58.649Z","repository":{"id":252632496,"uuid":"839692406","full_name":"do-/node-events-to-winston","owner":"do-","description":"Observing an EventEmitter, logging to winston","archived":false,"fork":false,"pushed_at":"2025-05-16T11:51:08.000Z","size":174,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-24T02:06:58.127Z","etag":null,"topics":["events","node","winston"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/do-.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-08T06:20:52.000Z","updated_at":"2025-05-16T11:51:12.000Z","dependencies_parsed_at":"2024-08-13T12:34:16.164Z","dependency_job_id":null,"html_url":"https://github.com/do-/node-events-to-winston","commit_stats":null,"previous_names":["do-/node-events-to-winston"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/do-/node-events-to-winston","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/do-%2Fnode-events-to-winston","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/do-%2Fnode-events-to-winston/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/do-%2Fnode-events-to-winston/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/do-%2Fnode-events-to-winston/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/do-","download_url":"https://codeload.github.com/do-/node-events-to-winston/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/do-%2Fnode-events-to-winston/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28662738,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T01:17:37.254Z","status":"online","status_checked_at":"2026-01-22T02:00:07.137Z","response_time":144,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["events","node","winston"],"created_at":"2024-12-04T14:09:55.818Z","updated_at":"2026-01-22T12:02:58.640Z","avatar_url":"https://github.com/do-.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![workflow](https://github.com/do-/node-events-to-winston/actions/workflows/main.yml/badge.svg)\n![Jest coverage](./badges/coverage-jest%20coverage.svg)\n\n`events-to-winston` is a node.js library for writing comprehensive logs like this\n```\n2025-05-04 18:29:23.416 def/e36e8968 \u003e App.doInit {\"request\":{\"type\":\"app\",\"action\":\"init\"}}\n2025-05-04 18:29:23.418 def/e36e8968 \u003c 4 ms\n2025-05-04 18:29:24.012 sch/a470d45f \u003e App.doTick {\"request\":{\"type\":\"app\",\"action\":\"tick\"}}\n2025-05-04 18:29:24.014 sch/a470d45f/80b72428 \u003e Message.doSend {\"request\":{\"type\":\"message\",\"action\":\"send\",\"id\":\"a470d45f\"}}\n2025-05-04 18:29:24.014 sch/a470d45f/80b72428 \u003c 1 ms\n2025-05-04 18:29:24.014 sch/a470d45f \u003c 5 ms\n```\nmostly automatically.\n\n# Motivation\n\nSuppose you develop a node.js application:\n* with lots of [`EventEmitter`](https://nodejs.org/docs/latest/api/events.html)s \n* using [`winston`](https://github.com/winstonjs/winston) for logging.\n\nMonths after the code is frozen and deployed on a production environment, you have to quickly answer questions like\n* why did that operation slow down?\n* at that precise moment, what was the cause of the crash?\n* why does that screen show X while Y is expected?\n\nYou probably want all necessary logging to be done by event listeners in and of itself, instead of random manually coded `logger.log (...)` calls. A configurable class for such listeners is provided by `events-to-winston`, with the addition of some suitable formatters.\n\n## IDs, Nesting\n\nWhen you deal with business process lifecycles, having [unique identifiers](#id) is crucial to analyze the history of related events: match error messages with previously reported parameter values etc.\n\nThe present module provides an easy way to [discover](#id-auto-discovery) such markers from the observable object's properties. And it's trivial to not only read local properties but to walk up the object hierarchy to build logging IDs as meaningful object paths like `${endpoint.id}/${httpRequest.id}/${sqlStatement.id}`.\n\n## Exposing Business Data\n\nAutomatic event recording is great, but has little meaning without showing what exactly these events are about. For an AJAX request, you want the path and the body, for a database call — the SQL text and parameter values, and so on. All [`details`](#details) like this are easily configured to appear in _info objects_.\n\nThey are available to use with ['printf'](https://github.com/winstonjs/logform?tab=readme-ov-file#printf) or likes, but here come some pesky problems:\n* it's obvious to print it as JSON, but [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) may throw errors;\n* text values happen to be megabytes long — and, unlike short IDs, they are barely valuable for analysis;\n  * verbatim copies of SOAP messages etc. must be stored elsewhere;\n* `Buffers`'s [`toJSON ()`](https://nodejs.org/docs/latest/api/buffer.html#buftojson) output is lossless, but lengthy and hardly readable while binary data make little to no sense for the task at hand;\n* dumps of [streams](https://nodejs.org/docs/latest/api/stream.html) and other system internals make reading logs even more difficult;\n* some short, but [sensitive](https://github.com/cabinjs/sensitive-fields/blob/master/index.json) strings (e.g. passwords) must be never recorded at all.\n\nTo better cope with these annoyances, a special [formatter](https://github.com/do-/node-events-to-winston/wiki/formatDetails) is provided.\n\n## Tagging Process Phases\n\nWhen looking at a running flow of lines depicting some processes' events, you may prefer to clearly see which ones are about starting and finishing workflow instances. For sure, it can be deduced from content, but this module proposes a simple tool making human eyes and brains a bit more comfortable by showing _sigils_: easy remarkable one character tags corresponding to generalized process [phases](https://github.com/do-/node-events-to-winston/wiki/formatPhase): `\u003e` at the beginning, `\u003c` at the end and so on.\n\n## Profiling\n\nThe native `winston`'s `logform` features the [`ms()`](https://github.com/winstonjs/logform/blob/master/ms.js) format showing the _number of milliseconds \u003cu\u003esince the previous\u003c/u\u003e log message_ which appears to be not much useable: when you do some profiling, random error and debug messages can mess up stats easily.\n\nTo address this issue, `events-to-winston` lets developers augment any finite life `EventEmitter` (e.g. an HTTP request) with a one shot [observer object](#in-depth) keeping, among other, the moment of its creation and able to calculate [`elapsed`](#elapsed) times for necessary events. The corresponding [formatter](https://github.com/do-/node-events-to-winston/wiki/formatElapsed) is included in the package.\n\n# Installation\n```sh\nnpm install events-to-winston\n```\n\n# Usage\n```js\nconst winston = require ('winston')\nconst {Tracker, formatDetails, formatElapsed, formatPhase} = require ('events-to-winston')\n\nconst logger = winston.createLogger (transports: [\n  new transports.Console ({\n    format: winston.format.combine (\n      winston.format.timestamp ({format: 'YYYY-MM-DD HH:mm:ss.SSS'}),\n      formatElapsed (),\n      formatPhase (),\n      formatDetails (),\n      winston.format.printf (info =\u003e `${info.timestamp} ${info.id} ${info.message}`)\n    )\n  }),\n])\n// const myEventEmitter = new MyEventEmitterClass (...)\n\n// myEventEmitter [Tracker.LOGGING_ID] = 1\n// myEventEmitter [Tracker.LOGGING_PARENT] = someParentObject // with `Tracker.LOGGING_ID` set\n\nconst tracker = new Tracker (emitter, logger, {\n//id: `process #${emitter.id}`, \n  events: {\n    progress: {\n      level: 'info',\n//    message: 'progress', // defaults to the event name\n//    elapsed: true,       // to set `info.elapsed`: ms since the `tracker` creation\n  // properties may be computable, `this` is myEventEmitter\n//    level:   function (payload) {return this.status == 10 \u0026\u0026 payload \u003c 50 ? 'notice' : 'info'},\n//    message: function (payload) {return `${this.id}: ${payload}%`},\n//    details: function (payload) {return {id: this.id, progress: payload}},\n    }\n  }\n//, maxPathLength: 100\n//, sep: '/'\n})\n\ntracker.listen ()\n\n// myEventEmitter.emit ('progress', 42)\n\n// tracker.unlisten ()\n```\n\n# In Depth\n\n`events-to-winston` features the [`Tracker`](https://github.com/do-/node-events-to-winston/wiki/Tracker) class: a tool for observing an arbitrary [`EventEmitter`](https://nodejs.org/docs/latest/api/events.html) with a given [`winston`](https://github.com/winstonjs/winston) logger.\n\nEach `Tracker` object listens to a given `emitter` and transforms incoming events to `winston`'s [_info objects_](https://github.com/winstonjs/winston?tab=readme-ov-file#streams-objectmode-and-info-objects), which are immediately fed to the specified `logger`.\n\nOne `logger` can be shared across multiple `Tracker` instances, but each of them must observe its own, unique `emitter`.\n\n`Tracker` is designed to be completely configurable, with 3 the tiered setup (implemented via [subclassable-object-merger](https://github.com/do-/node-subclassable-object-merger)):\n* unique options set at the instance creation;\n* `emitter` specific defaults available as its special properties;\n* the hardcoded common default settings.\n\nThis 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.\n\n# Info objects' properties, tracker settings\nAs 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.\n\n## `winston`'s Standard\n\n### `level`\nThis 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`.\n\n### `message`\nBy default, is copied from the event name. Otherwise, is copied as is or evaluated as a function, like `level`.\n\n## Extra\n\n### `details`\nIf configured, must be a plain [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) or a function returning plain objects — the latter is called the same way as for `level` and `message`. Similar, but different from `winston`'s [`metadata`](https://github.com/winstonjs/logform?tab=readme-ov-file#metadata). [`formatDetails`](https://github.com/do-/node-events-to-winston/wiki/formatDetails) is recommended for serialization.\n\n### `elapsed`\nIf the `elapsed` option is set for the event, `info.elapsed` is the number of milliseconds since the tracker instance was created. To generate `message` values based on the presence of this property, [`formatElapsed`](https://github.com/do-/node-events-to-winston/wiki/formatElapsed) is provided.\n\n### `event`\nThis property is always set as the copy of the `event`'s name.\n\n### `id`\nIf 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. Think `winston`'s [`label`](https://github.com/winstonjs/logform?tab=readme-ov-file#label), but local for each observable instance.\n\n### `isLast`\nIf this boolean option is configured, it's copied into the info object. If no event has this option set to `true`, then `info.isLast = true` when `elapsed` is set. That means, an event logged with the `elapsed` time should be the last in its lifecycle, but this can be overridden.\n\n### `isFirst`\nThis property is set to `true` for the first info object created by a `Tracker` instance. Otherwise, absent. Not configurable.\n\n# Default configuration\nA `Tracker` instance may be created without any configuration at all: with only `emitter` and `logger` parameters. In this case, the [`getDefaultEvents ()`](https://github.com/do-/node-events-to-winston/wiki/Tracker#getdefaultevents-) result will be used, that is\n```\n{\n  error: {\n    level: 'error', \n    message: error =\u003e error.stack, \n    details: error =\u003e error\n  },\n}\n```\nIf the `events` option is set explicitly, but lacks any mention of `'error'`, the default configuration is silently added.\n\nSo, at least [`'error'` events](https://nodejs.org/docs/latest/api/events.html#error-events) are tracked anyway (which makes sense due to their special properties, like the ability to shut down the entire runtime).\n\nThe explicit redefinition, partial or complete, is always available.\n\n# `emitter`'s own configuration\nThe `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).\n\nExample:\n```js\nclass MyClass extends EventEmitter {\n  get [Tracker.LOGGING_EVENTS] () {\n    return {\n      start:  {level: 'info'},\n      finish: {level: 'info', elapsed: true},\n    }\n  }\n}\n```\nSo, when actually creating a `Tracker` instance, the configuration is merged from three sources:\n* the 3rd constructor parameter (if any): highest priority;\n* `emitter.[Tracker.LOGGING_EVENTS]`: filling gaps;\n* finally the hardcoded default error handling (see the previous section), if left undefined.\n\n# `id` auto discovery\nWhile 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:\n* `[Tracker.LOGGING_ID]`: some scalar identifier value;\n* `[Tracker.LOGGING_PARENT]`: the optional reference to a parent object.\n\nIf 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`. \n\nExample:\n```js\nconst service = {}\nservice [Tracker.LOGGING_ID] = 'mySvc'\n\nconst request = new EventEmitter ()\nrequest [Tracker.LOGGING_PARENT] = service\nrequest [Tracker.LOGGING_ID] = '8faa4e0e-d079-4c80-2200-a4a6fc702535'\n\nconst tracker = new Tracker (request, logger)\n// tracker.id = 'mySvc/8faa4e0e-d079-4c80-2200-a4a6fc702535'\n```\n\nInstead of direct injection, classes using this `events-to-winston`'s feature should set the necessary properties in constructors or define accessor methods like\n\n```js\nclass MyService {\n  get [Tracker.LOGGING_ID] () {\n    return this.name\n  }\n}\nclass MyServiceRequest {\n  get [Tracker.LOGGING_PARENT] () {\n    return this.service\n  }\n  get [Tracker.LOGGING_ID] () {\n    return this.uuid\n  }\n}\n```\n\n# `details` declaration\nAlong with `info.id`, `info.details` is another [_meta_](https://github.com/winstonjs/winston?tab=readme-ov-file#streams-objectmode-and-info-objects) 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`:\n\n```js\nget [Tracker.LOGGING_DETAILS] () {\n  return {\n    const {parameters} = this\n    return {...super [Tracker.LOGGING_DETAILS], parameters}\n  }\n}\n```\nNote that `info.details` is set only for events with the `details` option configured, at least as an empty object:\n```js\nevents: {\n  start: {\n    level: 'info',\n    details: {}              // emitter[Tracker.LOGGING_DETAILS] will be used 1st in Object.assign()...\n  },\n  notice: {\n    level: 'info',\n    details: ({where}) =\u003e ({               \n      where,\n      parameters: undefined, // ...and may be overridden as needed\n    }),   \n  },\n  finish: {\n    level: 'info',\n    elapsed: true,           // here, no details at all\n  },\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdo-%2Fnode-events-to-winston","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdo-%2Fnode-events-to-winston","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdo-%2Fnode-events-to-winston/lists"}