Skip to content

Commit 7c0b34e

Browse files
authored
fix(winston, pino): a few crashes with req, res, event, and service fields (#71)
Fix a couple crashes with convertReqRes:true and logging a req/res that isn't an HTTP request or response object -- via update to ecs-helpers v1.1.0. Also fix some conflicts with logging top-level "event" and "service" fields with the winston formatter. Fixes: #58 Fixes: #59
1 parent 22263c1 commit 7c0b34e

File tree

9 files changed

+77
-17
lines changed

9 files changed

+77
-17
lines changed

loggers/morgan/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Update @elastic/ecs-helpers@1.1.0 to get more robust HTTP req and res
6+
formatting.
7+
58
- Add `apmIntegration: false` option to all ecs-logging formatters to
69
enable explicitly disabling Elastic APM integration.
710
([#62](https://github.com/elastic/ecs-logging-nodejs/pull/62))

loggers/morgan/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"node": ">=10"
3636
},
3737
"dependencies": {
38-
"@elastic/ecs-helpers": "^1.0.0"
38+
"@elastic/ecs-helpers": "^1.1.0"
3939
},
4040
"devDependencies": {
4141
"ajv": "^7.0.3",

loggers/pino/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- Fix a "TypeError: Cannot read property 'host' of undefined" crash when using
6+
`convertReqRes: true` and logging a `req` field that is not an HTTP request
7+
object.
8+
59
- Set the "message" to the empty string for logger calls that provide no
610
message, e.g. `log.info({foo: 'bar'})`. In this case pino will not add a
711
message field, which breaks ecs-logging spec.

loggers/pino/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"node": ">=10"
3535
},
3636
"dependencies": {
37-
"@elastic/ecs-helpers": "^1.0.0"
37+
"@elastic/ecs-helpers": "^1.1.0"
3838
},
3939
"devDependencies": {
4040
"ajv": "^7.0.3",

loggers/winston/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
## Unreleased
44

5+
- Fix a crash ([#58](https://github.com/elastic/ecs-logging-nodejs/issues/58))
6+
when using APM integration and logging a record with an "event" or
7+
"service" top-level field that isn't an object. The "fix" here is to
8+
silently discard that "event" or "service" field because a non-object
9+
conflicts with the ECS spec for those fields. (See
10+
[#68](https://github.com/elastic/ecs-logging-nodejs/issues/68) for a
11+
discussion of the general ECS type conflict issue.)
12+
13+
- Fix a crash ([#59](https://github.com/elastic/ecs-logging-nodejs/issues/59))
14+
when using `convertReqRes: true` and logging a `res` field that is not an
15+
HTTP response object.
16+
517
- Add `apmIntegration: false` option to all ecs-logging formatters to
618
enable explicitly disabling Elastic APM integration.
719
([#62](https://github.com/elastic/ecs-logging-nodejs/pull/62))

loggers/winston/index.js

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ function ecsTransform (info, opts) {
103103
// istanbul ignore else
104104
if (apm) {
105105
// Set "service.name" and "event.dataset" from APM conf, if not already set.
106-
let serviceName = ecsFields.service && ecsFields.service.name
106+
let serviceName = (ecsFields.service && ecsFields.service.name && typeof ecsFields.service.name === 'string'
107+
? ecsFields.service.name
108+
: undefined)
107109
if (!serviceName) {
108110
// https://github.com/elastic/apm-agent-nodejs/pull/1949 is adding
109111
// getServiceName() in v3.11.0. Fallback to private `apm._conf`.
@@ -114,13 +116,27 @@ function ecsTransform (info, opts) {
114116
// A mis-configured APM Agent can be "started" but not have a
115117
// "serviceName".
116118
if (serviceName) {
117-
ecsFields.service = ecsFields.service || {}
118-
ecsFields.service.name = serviceName
119+
if (ecsFields.service === undefined) {
120+
ecsFields.service = { name: serviceName }
121+
} else if (!isVanillaObject(ecsFields.service)) {
122+
// Warning: "service" type conflicts with ECS spec. Overwriting.
123+
ecsFields.service = { name: serviceName }
124+
} else {
125+
ecsFields.service.name = serviceName
126+
}
119127
}
120128
}
121-
if (serviceName && !(ecsFields.event && ecsFields.event.dataset)) {
122-
ecsFields.event = ecsFields.event || {}
123-
ecsFields.event.dataset = serviceName + '.log'
129+
if (serviceName &&
130+
!(ecsFields.event && ecsFields.event.dataset &&
131+
typeof ecsFields.event.dataset === 'string')) {
132+
if (ecsFields.event === undefined) {
133+
ecsFields.event = { dataset: serviceName + '.log' }
134+
} else if (!isVanillaObject(ecsFields.event)) {
135+
// Warning: "event" type conflicts with ECS spec. Overwriting.
136+
ecsFields.event = { dataset: serviceName + '.log' }
137+
} else {
138+
ecsFields.event.dataset = serviceName + '.log'
139+
}
124140
}
125141

126142
// https://www.elastic.co/guide/en/ecs/current/ecs-tracing.html
@@ -168,4 +184,19 @@ function ecsTransform (info, opts) {
168184
return info
169185
}
170186

187+
// Return true if the given arg is a "vanilla" object. Roughly the intent is
188+
// whether this is basic mapping of string keys to values that will serialize
189+
// as a JSON object.
190+
//
191+
// Currently, it excludes Map. The uses above don't really expect a user to:
192+
// service = new Map([["foo", "bar"]])
193+
// log.info({ service }, '...')
194+
//
195+
// There are many ways tackle this. See some attempts and benchmarks at:
196+
// https://gist.github.com/trentm/34131a92eede80fd2109f8febaa56f5a
197+
function isVanillaObject (o) {
198+
return (typeof o === 'object' &&
199+
(!o.constructor || o.constructor.name === 'Object'))
200+
}
201+
171202
module.exports = format(ecsTransform)

loggers/winston/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"node": ">=10"
3636
},
3737
"dependencies": {
38-
"@elastic/ecs-helpers": "^1.0.0"
38+
"@elastic/ecs-helpers": "^1.1.0"
3939
},
4040
"devDependencies": {
4141
"ajv": "^7.0.3",

loggers/winston/test/apm.test.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,17 @@ test('can override service.name, event.dataset', t => {
335335
const recs = stdout.trim().split(/\n/g).map(JSON.parse)
336336
t.equal(recs[0].service.name, 'myname')
337337
t.equal(recs[0].event.dataset, 'mydataset')
338+
339+
// If integrating with APM and the log record sets "service.name" to a
340+
// non-string or "service" to a non-object, then ecs-winston-format will
341+
// overwrite it because it conflicts with the ECS specified type.
338342
t.equal(recs[1].service.name, 'test-apm')
339343
t.equal(recs[1].event.dataset, 'test-apm.log')
344+
t.equal(recs[2].service.name, 'test-apm')
345+
t.equal(recs[2].event.dataset, 'test-apm.log')
346+
347+
t.equal(recs[3].service.name, 'test-apm')
348+
t.equal(recs[3].event.dataset, 'test-apm.log')
340349
t.end()
341350
})
342351
})
@@ -355,8 +364,8 @@ test('unset APM serviceName does not set service.name, event.dataset, but also d
355364
const recs = stdout.trim().split(/\n/g).map(JSON.parse)
356365
t.equal(recs[0].service.name, 'myname')
357366
t.equal(recs[0].event.dataset, 'mydataset')
358-
t.equal(recs[1].service, undefined)
359-
t.equal(recs[1].event, undefined)
367+
t.equal(recs[3].service, undefined)
368+
t.equal(recs[3].event, undefined)
360369
t.end()
361370
})
362371
})

loggers/winston/test/use-apm-override-service-name.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ const log = winston.createLogger({
4545
]
4646
})
4747

48-
log.info('hi', {
49-
foo: 'bar',
50-
service: { name: 'myname' },
51-
event: { dataset: 'mydataset' }
52-
})
53-
log.info('bye', { foo: 'bar' })
48+
log.info('override values',
49+
{ service: { name: 'myname' }, event: { dataset: 'mydataset' } })
50+
log.info('override values with nums',
51+
{ service: { name: 42 }, event: { dataset: 42 } })
52+
log.info('override top-level keys with invalid ECS type',
53+
{ service: 'myname', event: 'myevent' })
54+
log.info('bye')

0 commit comments

Comments
 (0)