forked from blimmer/logform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
splat.js
132 lines (115 loc) · 4.17 KB
/
splat.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
'use strict';
const util = require('util');
const { SPLAT } = require('triple-beam');
/**
* Captures the number of format (i.e. %s strings) in a given string.
* Based on `util.format`, see Node.js source:
* https://github.com/nodejs/node/blob/b1c8f15c5f169e021f7c46eb7b219de95fe97603/lib/util.js#L201-L230
* @type {RegExp}
*/
const formatRegExp = /%[scdjifoO%]/g;
/**
* Captures the number of escaped % signs in a format string (i.e. %s strings).
* @type {RegExp}
*/
const escapedPercent = /%%/g;
class Splatter {
constructor(opts) {
this.options = opts;
}
/**
* Check to see if tokens <= splat.length, assign { splat, meta } into the
* `info` accordingly, and write to this instance.
*
* @param {Info} info Logform info message.
* @param {String[]} tokens Set of string interpolation tokens.
* @returns {Info} Modified info message
* @private
*/
_splat(info, tokens) {
const msg = info.message;
const splat = info[SPLAT] || info.splat || [];
const percents = msg.match(escapedPercent);
const escapes = percents && percents.length || 0;
// The expected splat is the number of tokens minus the number of escapes
// e.g.
// - { expectedSplat: 3 } '%d %s %j'
// - { expectedSplat: 5 } '[%s] %d%% %d%% %s %j'
//
// Any "meta" will be arugments in addition to the expected splat size
// regardless of type. e.g.
//
// logger.log('info', '%d%% %s %j', 100, 'wow', { such: 'js' }, { thisIsMeta: true });
// would result in splat of four (4), but only three (3) are expected. Therefore:
//
// extraSplat = 3 - 4 = -1
// 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)
: [];
// 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);
return info;
}
/**
* Transforms the `info` message by using `util.format` to complete
* any `info.message` provided it has string interpolation tokens.
* If no tokens exist then `info` is immutable.
*
* @param {Info} info Logform info message.
* @param {Object} opts Options for this instance.
* @returns {Info} Modified info message
*/
transform(info) {
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;
}
// 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]);
}
}
return info;
}
if (tokens) {
return this._splat(info, tokens);
}
return info;
}
}
/*
* function splat (info)
* Returns a new instance of the splat format TransformStream
* which performs string interpolation from `info` objects. This was
* previously exposed implicitly in `winston < 3.0.0`.
*/
module.exports = opts => new Splatter(opts);