Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

splat refactor and add metadataArray #85

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 70 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const alignedWithColorsAndTime = format.combine(
- [Label](#label)
- [Logstash](#logstash)
- [Metadata](#metadata)
- [Metadata Array](#metadataArray)
- [PadLevels](#padlevels)
- [PrettyPrint](#prettyprint)
- [Printf](#printf)
Expand Down Expand Up @@ -451,6 +452,8 @@ It accepts the following options:
* **fillExcept**: An array of keys that should not be added to the metadata object.
* **fillWith**: An array of keys that will be added to the metadata object.

Do not use this simultaneously with metadataArray as they both process the same information differently. The key/values pairs must be subsequently exposed with json() or a custom printf() format.

```js
const { format } = require('logform');

Expand All @@ -464,6 +467,71 @@ const info = metadataFormat.transform({

console.log(info);
// { level: 'info', message: 'my message', metadata: { meta: 42 } }

const info2 = metadataFormat.transform({
level: 'info',
message: 'my message',
[SPLAT]: [{meta: 42}]
});

// above could be written with Winston as:
// logger.info('my message', {meta: 42})

console.log(info2);
// { level: 'info', message: 'my message', metadata: { meta: 42 } }
```

### Metadata Array

The `metadataArray` format stringifies metadata (info[SPLAT]) into info.message. It will stringify Errors, objects/arrays, and other data types found in info[SPLAT].
It accepts the following options:

* **messageSplatDelimeter**: A string to separate the meassage from the splat. Defaults to ` `.
* **innerSplatDelimeter**: A delimeter to be used to separate multiple splat. Defaults to `, `.

This works well in conjunction with splat. Splat will now take any "extra" matadata and leave it in info[SPLAT]. Do not use this simultaneously with metadata as they both process the same information differently.
```js
logger.info("my message %s", "should be one", {an:"object"})
/* after splat transform;
info: {
level: "info",
message: "my message should be one",
[SPLAT]: [{an:"object"}]
}
*/
```

```js
const { format } = require('logform');

const metadataArray = format.metadataArray();

const info = metadataArray.transform({
level: 'info',
message: 'my message',
[SPLAT]: [{an:"object"}]
});

console.log(info);
// { level: 'info', message: 'my message {"an":"object"} }

const info2 = metadataArray.transform({
level: 'info',
message: 'my message',
[SPLAT]: [{an:"object"}, [1, 2, 3], throw new('my bad!')]
}, { messageSplatDelimeter = ' ', innerSplatDelimeter = ', ' });

console.log(info);
// { level: 'info', message: 'my message:\n\t{"an":"object"}\n\t[1, 2, 3]}
// printed info.message...
/*
info: my message:
{"an":"object"}
[1, 2, 3]
Error: my bad!
at {...rest of stack}
*/

```

### PadLevels
Expand Down Expand Up @@ -580,8 +648,7 @@ console.log(info);
```

Any additional splat parameters beyond those needed for the `%` tokens
(aka "metas") are assumed to be objects. Their enumerable properties are
merged into the `info`.
are left in info[SPLAT] in an array for later processing. (See metadata or metadataArray)

```js
const { format } = require('logform');
Expand All @@ -597,8 +664,7 @@ const info = splatFormat.transform({
console.log(info);
// { level: 'info',
// message: 'my message test',
// thisIsMeta: true,
// splat: [ 'test' ] }
// [SPLAT]: [{thisIsMeta: true}],
```

This was previously exposed implicitly in `winston < 3.0.0`.
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ exposeFormat('simple');
exposeFormat('splat');
exposeFormat('timestamp');
exposeFormat('uncolorize');
exposeFormat('metadataArray');
14 changes: 14 additions & 0 deletions metadata.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const format = require('./format');
const { SPLAT } = require('triple-beam');

function fillExcept(info, fillExceptKeys, metadataKey) {
const savedKeys = fillExceptKeys.reduce((acc, key) => {
Expand Down Expand Up @@ -34,11 +35,24 @@ function fillWith(info, fillWithKeys, metadataKey) {
* object in winston 2.x.
*/
module.exports = format((info, opts = {}) => {


let metadataKey = 'metadata';
if (opts.key) {
metadataKey = opts.key;
}

// this assists with situations where the hotpath is not taken
// and the user provides 1+ object in the format
// logger.info('message', obj1, obj2, etc)
// Logger.js#234 will push obj1, obj2, etc to info[SPLAT]
const splat = info[SPLAT] || [];
if (splat.length) {
splat.forEach((item) => {
Object.assign(info, item);
});
}

let fillExceptKeys = [];
if (!opts.fillExcept && !opts.fillWith) {
fillExceptKeys.push('level');
Expand Down
52 changes: 52 additions & 0 deletions metadataArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

const { SPLAT } = require('triple-beam');
const format = require('./format');
const jsonStringify = require('fast-safe-stringify');

/**
* Transforms the `info` object by adding info[meta] from any
* unused info[SPLAT] items.
*
* @param {Info} info Logform info message.
* @param {Object} opts Options for this instance.
* @returns {Info} Modified info message
*/

module.exports = format((info, { messageSplatDelimeter = ' ', innerSplatDelimeter = ', ' }) => {
// not including info.splat here because if the user takes the hot path
// they will most likely not (aka shouldn't) include additional objects inside splat
const splat = info[SPLAT] || [];

// if no splat, return
if ((!splat.length)) {
return info;

}

let splatStringified = '';
info[SPLAT].forEach(function (el, idx) {
// add delimeter before al elements after the first
if (idx !== 0) {
splatStringified += innerSplatDelimeter;
}
// we can even parse for errors
if (el instanceof Error) {
splatStringified += `${el.stack}`;
} else if (typeof el === 'object') {
// standard string/array objects
splatStringified += jsonStringify(el);
} else {
// strings, numbers, etc
splatStringified += el;
}

});
info.message = `${info.message}${messageSplatDelimeter}${splatStringified}`;
delete info[SPLAT];
return info;


});


57 changes: 16 additions & 41 deletions splat.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,11 @@ class Splatter {
// metas = [100, 'wow', { such: 'js' }, { thisIsMeta: true }].splice(-1, -1 * -1);
// splat = [100, 'wow', { such: 'js' }]
const expectedSplat = tokens.length - escapes;
const extraSplat = expectedSplat - splat.length;
const metas = extraSplat < 0
? splat.splice(extraSplat, -1 * extraSplat)
const splatMerge = expectedSplat > 0
? splat.splice(0, expectedSplat)
: [];

// Now that { splat } has been separated from any potential { meta }. we
// can assign this to the `info` object and write it to our format stream.
// If the additional metas are **NOT** objects or **LACK** enumerable properties
// you are going to have a bad time.
const metalen = metas.length;
if (metalen) {
for (let i = 0; i < metalen; i++) {
Object.assign(info, metas[i]);
}
}

info.message = util.format(msg, ...splat);
info.message = util.format(msg, ...splatMerge);
return info;
}

Expand All @@ -82,44 +70,31 @@ class Splatter {
* @returns {Info} Modified info message
*/
transform(info) {
// look for info.mesasge fist. if it isn't set then set it from info[MESSAGE]
const msg = info.message;
const splat = info[SPLAT] || info.splat;

// No need to process anything if splat is undefined
if (!splat || !splat.length) {
return info;
// info[SPLAT] will be preset if user uses logger.log('level', 'message', ...)
// info.splat will be used in hot path (eg logger.log({level: 'level', message: 'msg %s', splat: 'string'}))
let splat = info[SPLAT] || info.splat;
// following checks if hotpath is taken and splat is not passed in an array
if (!Array.isArray(splat)) {
splat = [splat];
// and write back to info object so we don't need to check twice (once here, once in _splat)
info[SPLAT] = splat;
}

// Extract tokens, if none available default to empty array to
// ensure consistancy in expected results
const tokens = msg && msg.match && msg.match(formatRegExp);

// This condition will take care of inputs with info[SPLAT]
// but no tokens present
if (!tokens && (splat || splat.length)) {
const metas = splat.length > 1
? splat.splice(0)
: splat;

// Now that { splat } has been separated from any potential { meta }. we
// can assign this to the `info` object and write it to our format stream.
// If the additional metas are **NOT** objects or **LACK** enumerable properties
// you are going to have a bad time.
const metalen = metas.length;
if (metalen) {
for (let i = 0; i < metalen; i++) {
Object.assign(info, metas[i]);
}
}
// No need to process anything if splat is undefined
if (!splat || !splat.length || !tokens) {

return info;
}

if (tokens) {
return this._splat(info, tokens);
}
return this._splat(info, tokens);


return info;
}
}

Expand Down
26 changes: 26 additions & 0 deletions test/metadata.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const assume = require('assume');
const metadata = require('../metadata');
const helpers = require('./helpers');
const { SPLAT } = require('triple-beam');

describe('metadata', () => {
const testInfoObject = {
Expand All @@ -14,6 +15,17 @@ describe('metadata', () => {
}
};

const testInfoObjectSplat = {
level: 'info',
message: 'whatever',
[SPLAT]: [{ someKey: 'someValue' },
{
someObject: {
key: 'value'
}
}]
};

it('metadata() (default) removes message and level and puts everything else into metadata', helpers.assumeFormatted(
metadata(),
testInfoObject,
Expand All @@ -27,6 +39,19 @@ describe('metadata', () => {
}
));

it('[SPLAT]: metadata() (default) removes message and level and puts everything else into metadata', helpers.assumeFormatted(
metadata(),
testInfoObjectSplat,
info => {
assume(info.level).is.a('string');
assume(info.message).is.a('string');
assume(info.metadata).is.a('object');
assume(info.metadata.someKey).equals('someValue');
assume(info.metadata.someObject).is.a('object');
assume(info.metadata.someObject.key).equals('value');
}
));

it('metadata({ fillWith: [keys] }) only adds specified keys to the metadata object', helpers.assumeFormatted(
metadata({ fillWith: ['level', 'someObject'] }),
testInfoObject,
Expand Down Expand Up @@ -68,4 +93,5 @@ describe('metadata', () => {
assume(info.myCustomKey.level).equals('info');
}
));

});
Loading