diff --git a/README.md b/README.md index 30fda6a..2b15ac8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # good-map Transform stream for [Hapi Good](https://github.com/hapijs/good) process monitoring. -Properties of the incoming object stream can be modified with your own mapping functions. +Modify the incoming object stream with your own mapping functions. [![Build Status](https://travis-ci.org/frankthelen/good-map.svg?branch=master)](https://travis-ci.org/frankthelen/good-map) [![Coverage Status](https://coveralls.io/repos/github/frankthelen/good-map/badge.svg?branch=master)](https://coveralls.io/github/frankthelen/good-map?branch=master) @@ -63,10 +63,11 @@ await server.start(); The `good-map` transform stream can be placed anywhere in the pipeline where log values are still objects, e.g., after `Squeeze`. -`GoodMap(rules, [options])` has the following parameters: +`args` in the Good configuration for `good-map` is an array with two arguments which are passed to `GoodMap(rules, [options])`: - * `rules`: An object with the following parameters: - - `events`: An optional list of Hapi server events, e.g., `['request']`, for filtering purposes. - - `tags`: An optional list of event tags, e.g., `['error']`, for filtering purposes. - - `map`: An object mapping log item properties (including deep properties separated by dots), e.g., `timestamp` or `error.message`, using a mapping function of the form `value => 'newValue'`, e.g., `() => '***'` or `str => str.toUpperCase()`. If a property does not exist (before), it will *not* be set. If the mapping function returns `undefined`, the property will be deleted. If the mapping function throws an error (for whatever reason), the error will be suppressed. - * `options`: Optional configuration object that gets passed to the Node [Stream.Transform](http://nodejs.org/api/stream.html#stream_class_stream_transform) constructor. `objectMode` is always `true`. + * `rules`: An object with the following parameters (all optional): + - `events`: A list of Hapi server event types, e.g., `['request']`, for filtering purposes. + - `tags`: A list of event tags, e.g., `['error']`, for filtering purposes. + - `map`: An object that maps the log item's properties (including deep properties separated by dots), e.g., `timestamp` or `'error.message'` to a mapping function (synchronous). It has the form `(value) => 'newValue'`, e.g., `() => '***'` or `str => str.toUpperCase()`. If a property does not exist (before), it is *not* set. If the mapping function returns `undefined`, the property is deleted. If the mapping function throws an error, the error is ignored. For full flexibility, the second parameter provides access to the complete log item: `(value, item) => ...`. + - `observe`: Listen to the complete log item as it appears in the stream. The observer function (synchronous) has the form `(item) => { ... }`. If the observer function throws an error, the error is ignored. + * `options`: An optional configuration object that gets passed to the Node [Stream.Transform](http://nodejs.org/api/stream.html#stream_class_stream_transform) constructor. `objectMode` is always `true`. diff --git a/package.json b/package.json index cd0cd7a..d95075c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "good-map", - "version": "1.0.0", + "version": "1.1.0", "description": "Transform stream for Hapi Good process monitoring", "main": "src/index.js", "repository": { diff --git a/src/index.js b/src/index.js index 4ab3702..9fd29dc 100644 --- a/src/index.js +++ b/src/index.js @@ -6,13 +6,17 @@ const unset = require('lodash.unset'); class GoodMap extends Stream.Transform { constructor(rules = {}, options = {}) { super({ ...options, objectMode: true }); - const { events = [], tags = [], map = {} } = rules; + const { + events = [], tags = [], map = {}, observe = () => {}, + } = rules; this.events = events; this.tags = tags; this.map = map; this.props = Object.keys(map); + this.observe = observe; } _transform(data, enc, next) { + // filter const { event, tags = [] } = data; if (this.events.length && !this.events.includes(event)) { return next(null, data); @@ -20,13 +24,14 @@ class GoodMap extends Stream.Transform { if (this.tags.length && !this.tags.reduce((acc, tag) => acc || tags.includes(tag), false)) { return next(null, data); } + // map properties this.props.forEach((prop) => { try { const value = get(data, prop); if (value === undefined) { // do nothing } else { - const newValue = this.map[prop](value); + const newValue = this.map[prop](value, data); if (newValue === undefined) { unset(data, prop); } else { @@ -37,6 +42,13 @@ class GoodMap extends Stream.Transform { // ignore } }); + // observe + try { + this.observe(data); + } catch (error) { + // ignore + } + // continue return next(null, data); } } diff --git a/test/index.spec.js b/test/index.spec.js index 7702bdb..d71a11b 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -133,4 +133,36 @@ describe('GoodMap', () => { expect(result.length).to.equal(3); expect(result[2]).to.not.have.property('timestamp'); }); + + it('should provide the log item in the mapping function', async () => { + const collect = []; + const options = { + map: { + prop: (value, item) => { collect.push(item); return true; }, + }, + }; + await pipe(options, testEvents); + expect(collect.length).to.equal(3); + expect(collect[0]).to.deep.equal({ ...testEvents[0], prop: true }); + expect(collect[1]).to.deep.equal({ ...testEvents[1], prop: true }); + expect(collect[2]).to.deep.equal({ ...testEvents[2], prop: true }); + }); + + it('should call the observer function after mapping', async () => { + const collect = []; + const options = { + map: { + prop: () => true, + }, + observe: (item) => { + item.bla = true; // eslint-disable-line no-param-reassign + collect.push(item); + }, + }; + await pipe(options, testEvents); + expect(collect.length).to.equal(3); + expect(collect[0]).to.deep.equal({ ...testEvents[0], prop: true, bla: true }); + expect(collect[1]).to.deep.equal({ ...testEvents[1], prop: true, bla: true }); + expect(collect[2]).to.deep.equal({ ...testEvents[2], prop: true, bla: true }); + }); });