Skip to content

Commit e5cde5c

Browse files
authored
fix(winston): do not remove info.level field (#174)
Winston's logform docs state that 'info' objects should always have 'level' and 'message' fields. Some downstream transforms and transports can rely on this. Instead the 'ecsStringify' will handle excluding the 'level' field from the stringification. Closes: #173
1 parent 48195db commit e5cde5c

File tree

4 files changed

+61
-20
lines changed

4 files changed

+61
-20
lines changed

docs/winston.asciidoc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,22 @@ const logger = winston.createLogger({
339339
** `eventDataset` +{type-string}+ A "event.dataset" value. If specified this overrides the default of using `${serviceVersion}`.
340340

341341
Create a formatter for winston that converts fields on the log record info
342-
objecct to ECS Logging format.
342+
object to ECS Logging format.
343343

344344
[float]
345345
[[winston-ref-ecsStringify]]
346346
==== `ecsStringify([options])`
347347

348348
Create a formatter for winston that stringifies/serializes the log record to
349-
JSON. (This is very similar to `logform.json()`.)
349+
JSON.
350+
351+
This is similar to `logform.json()`. They both use the `safe-stable-stringify`
352+
package to produce the JSON. Some differences:
353+
354+
* This stringifier skips serializing the `level` field, because it is not
355+
an ECS field.
356+
* Winston provides a `replacer` that converts bigints to strings The argument
357+
*for* doing so is that a *JavaScript* JSON parser looses precision when
358+
parsing a bigint. The argument against is that a BigInt changes type to a
359+
string rather than a number. For now this stringifier does not convert
360+
BitInts to strings.

packages/ecs-winston-format/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @elastic/ecs-winston-format Changelog
22

3+
## v1.5.2
4+
5+
- Fix the Winston transformers to *not* delete the `level` property from the
6+
`info` object, because https://github.com/winstonjs/logform#info-objects says
7+
"Every `info` must have at least the `level` and `message` properties".
8+
(https://github.com/elastic/ecs-logging-nodejs/issues/173)
9+
310
## v1.5.0
411

512
- Add `ecsFields` and `ecsStringify` exports that are winston formatters

packages/ecs-winston-format/index.js

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,6 @@ try {
3535
// Silently ignore.
3636
}
3737

38-
// Comparison of Winston's `logform.json()` for JSON serialization and
39-
// `ecsStringify()`.
40-
// - They both use `safe-stable-stringify`.
41-
// - Winston's exposes its `safe-stable-stringify` options, but doesn't document
42-
// this. (https://github.com/winstonjs/logform#json)
43-
// - Winston provides a `replacer` that converts bigints to strings. Doing
44-
// that is debatable. The argument *for* is that a *JavaScript* JSON parser
45-
// looses precision when parsing a bigint. The argument against is that a
46-
// BigInt changes type to a string rather than a number.
47-
// TODO: These differences should make it to docs somewhere.
4838
const stringify = safeStableStringify.configure()
4939

5040
/**
@@ -183,13 +173,10 @@ class EcsFieldsTransform {
183173
// Core ECS logging fields.
184174
info['@timestamp'] = new Date().toISOString()
185175
info['log.level'] = info.level
186-
// Removing 'level' might cause trouble for downstream winston formatters
187-
// given that https://github.com/winstonjs/logform#info-objects says:
188-
//
189-
// > Every info must have at least the level and message properties:
190-
//
191-
// However info still has a `info[Symbol.for('level')]` for more reliable use.
192-
delete info.level
176+
// Note: We do *not* remove `info.level`, even though it is not an ECS
177+
// field, because https://github.com/winstonjs/logform#info-objects says:
178+
// "Every info must have at least the level and message properties".
179+
// Instead, it will be excluded from serialization in `EcsStringifyTransform`.
193180
info['ecs.version'] = version
194181

195182
let apm = null
@@ -290,7 +277,11 @@ class EcsStringifyTransform {
290277
}
291278

292279
transform (info, opts) {
293-
info[MESSAGE] = stringify(info)
280+
// `info.level` must stay (see note above), but we don't want to serialize
281+
// it, so exclude it from the stringified fields. There *is* a perf cost
282+
// for this.
283+
const { level, ...infoSansLevel } = info
284+
info[MESSAGE] = stringify(infoSansLevel)
294285
return info
295286
}
296287
}

packages/ecs-winston-format/test/basic.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,38 @@ test('Should set expected @timestamp, "log.level", message', t => {
8585
t.end()
8686
})
8787

88+
// Per https://github.com/winstonjs/logform#info-objects
89+
// "Every info must have at least the level and message properties"
90+
test('Log record "info" objects should still have info.level and info.message', t => {
91+
class CheckInfoTransform {
92+
constructor (opts) {
93+
this.options = opts
94+
}
95+
96+
transform (info, _opts) {
97+
t.ok(info.message, 'info.message exists')
98+
t.ok(info.level, 'info.level exists')
99+
return info
100+
}
101+
}
102+
103+
const cap = new CaptureTransport()
104+
const logger = winston.createLogger({
105+
format: winston.format.combine(
106+
ecsFormat(),
107+
new CheckInfoTransform()
108+
),
109+
transports: [cap]
110+
})
111+
112+
logger.info('hi')
113+
// Should *not* have a 'level' field.
114+
const rec = cap.records[0]
115+
t.notOk(rec.level, 'should not have a "level" field')
116+
117+
t.end()
118+
})
119+
88120
test('Should append additional fields to the log record', t => {
89121
const cap = new CaptureTransport()
90122
const logger = winston.createLogger({

0 commit comments

Comments
 (0)