Skip to content

Commit 3288a1b

Browse files
authored
fix(helpers): make formatHttpRequest and formatHttpResponse more defensive (#69)
This also changes those and formatError to return a bool for whether the given obj was processed. The loggers will be able to use this to control passing through an unprocessed special field. Refs: #58
1 parent da05565 commit 3288a1b

File tree

7 files changed

+97
-10
lines changed

7 files changed

+97
-10
lines changed

helpers/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# @elastic/ecs-helpers Changelog
22

3+
## Unreleased
4+
5+
- Fix `formatHttpRequest` and `formatHttpResponse` to be more defensive. If
6+
the given request or response object, respectively, is not one they know
7+
how to process (for example if a user logs a `req` or `res` field that
8+
is not a Node.js http request or response object), then processing is skipped.
9+
The functions now return true if the given object could be processed,
10+
false otherwise. `formatError` was similarly changed to return true/false for
11+
whether the given `err` could be processed.
12+
313
## v1.0.0
414

515
- Change `formatHttpRequest` and `formatHttpResponse` to no longer exclude

helpers/README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ of objects with circular references. This generally means that ecs-logging-nodej
4646
libraries will throw a "Converting circular structure to JSON" exception if an
4747
attempt is made to log an object with circular references.
4848

49-
### `formatError(obj, err)`
49+
### `formatError(obj, err) -> bool`
5050

5151
A function that adds [ECS Error fields](https://www.elastic.co/guide/en/ecs/current/ecs-error.html)
52-
for a given `Error` object.
52+
for a given `Error` object. It returns true iff the given `err` was an Error
53+
object it could process.
5354

5455
```js
5556
const { formatError } = require('@elastic/ecs-helpers')
@@ -79,14 +80,19 @@ metadata field passed to a logging statement. E.g.
7980
`log.warn({err: myErr}, '...')` for pino, `log.warn('...', {err: myErr})`
8081
for winston.
8182

82-
### `formatHttpRequest(obj, req)`
83+
### `formatHttpRequest(obj, req) -> bool`
8384

8485
Function that enhances an ECS object with http request data.
8586
The given request object, `req`, must be one of the following:
8687
- Node.js's core [`http.IncomingMessage`](https://nodejs.org/api/all.html#http_class_http_incomingmessage),
8788
- [Express's request object](https://expressjs.com/en/5x/api.html#req) that extends IncomingMessage, or
8889
- a [hapi request object](https://hapi.dev/api/#request).
8990

91+
The function returns true iff the given `req` was a request object it could
92+
process. Note that currently this notably does not process a
93+
[`http.ClientRequest`](https://nodejs.org/api/all.html#http_class_http_clientrequest)
94+
as returned from `http.request()`.
95+
9096
```js
9197
const http = require('http')
9298
const { formatHttpRequest } = require('@elastic/ecs-helpers')
@@ -138,6 +144,13 @@ The given request object, `req`, must be one of the following:
138144
- [Express's response object](https://expressjs.com/en/5x/api.html#res) that extends ServerResponse, or
139145
- a [hapi **request** object](https://hapi.dev/api/#request)
140146

147+
The function returns true iff the given `res` was a response object it could
148+
process. Note that currently this notably does not process a
149+
[`http.IncomingMessage`](https://nodejs.org/api/all.html#http_class_http_incomingmessage)
150+
that is the argument to the
151+
["response" event](https://nodejs.org/api/all.html#http_event_response) of a
152+
[client `http.request()`](https://nodejs.org/api/all.html#http_http_request_options_callback)
153+
141154
```js
142155
const http = require('http')
143156
const { formatHttpRequest } = require('@elastic/ecs-helpers')

helpers/lib/error-formatters.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ const { toString } = Object.prototype
2121

2222
// Format an Error instance into ECS-compatible fields on the `ecs` object.
2323
// https://www.elastic.co/guide/en/ecs/current/ecs-error.html
24+
// Return true iff the given `err` was an Error object that could be processed.
2425
function formatError (ecsFields, err) {
2526
if (!(err instanceof Error)) {
2627
ecsFields.err = err
27-
return
28+
return false
2829
}
2930

3031
ecsFields.error = {
@@ -34,6 +35,8 @@ function formatError (ecsFields, err) {
3435
message: err.message,
3536
stack_trace: err.stack
3637
}
38+
39+
return true
3740
}
3841

3942
module.exports = { formatError }

helpers/lib/http-formatters.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,23 @@
1717

1818
'use strict'
1919

20+
// Write ECS fields for the given HTTP request (expected to be
21+
// `http.IncomingMessage`-y) into the `ecs` object. This returns true iff
22+
// the given `req` was a request object it could process.
2023
function formatHttpRequest (ecs, req) {
24+
if (req === undefined || req === null || typeof req !== 'object') {
25+
return false
26+
}
2127
if (req.raw && req.raw.req && req.raw.req.httpVersion) {
2228
// This looks like a hapi request object (https://hapi.dev/api/#request),
2329
// use the raw Node.js http.IncomingMessage that it references.
2430
// TODO: Use hapi's already parsed `req.url` for speed.
2531
req = req.raw.req
2632
}
33+
// Use duck-typing to check this is a `http.IncomingMessage`-y object.
34+
if (!('httpVersion' in req && 'headers' in req && 'method' in req)) {
35+
return false
36+
}
2737

2838
const {
2939
id,
@@ -103,14 +113,26 @@ function formatHttpRequest (ecs, req) {
103113
ecs.user_agent.original = headers['user-agent']
104114
}
105115
}
116+
117+
return true
106118
}
107119

120+
// Write ECS fields for the given HTTP response (expected to be
121+
// `http.ServiceResponse`-y) into the `ecs` object. This returns true iff
122+
// the given `res` was a response object it could process.
108123
function formatHttpResponse (ecs, res) {
124+
if (res === undefined || res === null || typeof res !== 'object') {
125+
return false
126+
}
109127
if (res.raw && res.raw.res && typeof (res.raw.res.getHeaders) === 'function') {
110128
// This looks like a hapi request object (https://hapi.dev/api/#request),
111129
// use the raw Node.js http.ServerResponse that it references.
112130
res = res.raw.res
113131
}
132+
// Use duck-typing to check this is a `http.ServerResponse`-y object.
133+
if (!('statusCode' in res && typeof res.getHeaders === 'function')) {
134+
return false
135+
}
114136

115137
const { statusCode } = res
116138
ecs.http = ecs.http || {}
@@ -129,6 +151,8 @@ function formatHttpResponse (ecs, res) {
129151
ecs.http.response.body.bytes = cLen
130152
}
131153
}
154+
155+
return true
132156
}
133157

134158
module.exports = { formatHttpRequest, formatHttpResponse }

helpers/test/basic.test.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'use strict'
1919

2020
const http = require('http')
21+
const { inspect } = require('util')
2122

2223
const addFormats = require('ajv-formats').default
2324
const Ajv = require('ajv').default
@@ -144,8 +145,10 @@ test('formatHttpRequest and formatHttpResponse should return a valid ecs object'
144145
// add anchor
145146
req.url += '#anchor'
146147

147-
formatHttpRequest(ecs, req)
148-
formatHttpResponse(ecs, res)
148+
let rv = formatHttpRequest(ecs, req)
149+
t.ok(rv, 'formatHttpRequest processed req')
150+
rv = formatHttpResponse(ecs, res)
151+
t.ok(rv, 'formatHttpResponse processed res')
149152

150153
const line = JSON.parse(stringify(ecs))
151154
t.ok(validate(line))
@@ -192,6 +195,36 @@ test('formatHttpRequest and formatHttpResponse should return a valid ecs object'
192195
}
193196
})
194197

198+
test('format* should not process non-req/res/err values', t => {
199+
const inputs = [
200+
null,
201+
'hi',
202+
42,
203+
{},
204+
[]
205+
]
206+
let obj
207+
let rv
208+
inputs.forEach(input => {
209+
obj = {}
210+
rv = formatError(obj, input)
211+
t.strictEqual(rv, false, `formatError did not process input: ${inspect(input)}`)
212+
// Cannot test that obj is unmodified because `formatError` sets obj.err.
213+
// See https://github.com/elastic/ecs-logging-nodejs/issues/66 to change that.
214+
215+
obj = {}
216+
rv = formatHttpRequest(obj, input)
217+
t.strictEqual(rv, false, `formatHttpRequest did not process input: ${inspect(input)}`)
218+
t.equal(Object.keys(obj).length, 0, `obj was not modified: ${inspect(obj)}`)
219+
220+
obj = {}
221+
rv = formatHttpResponse(obj, input)
222+
t.strictEqual(rv, false, `formatHttpResponse did not process input: ${inspect(input)}`)
223+
t.equal(Object.keys(obj).length, 0, `obj was not modified: ${inspect(obj)}`)
224+
})
225+
t.end()
226+
})
227+
195228
test('Should export a valid version', t => {
196229
t.ok(semver.valid(version))
197230
t.end()

helpers/test/express.test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ test('express res/req serialization', t => {
3838
res.write('hi')
3939
res.end()
4040

41-
formatHttpRequest(rec, req)
42-
formatHttpResponse(rec, res)
41+
let rv = formatHttpRequest(rec, req)
42+
t.ok(rv, 'formatHttpRequest processed req')
43+
rv = formatHttpResponse(rec, res)
44+
t.ok(rv, 'formatHttpResponse processed res')
4345

4446
t.deepEqual(rec.user_agent, { original: 'cool-agent' })
4547
t.deepEqual(rec.url, {

helpers/test/hapi.test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ test('hapi res/req serialization', testOpts, t => {
4848

4949
server.events.on('response', (request) => {
5050
const rec = {}
51-
formatHttpRequest(rec, request)
52-
formatHttpResponse(rec, request)
51+
let rv = formatHttpRequest(rec, request)
52+
t.ok(rv, 'formatHttpRequest processed request')
53+
rv = formatHttpResponse(rec, request)
54+
t.ok(rv, 'formatHttpResponse processed request')
5355

5456
t.deepEqual(rec.user_agent, { original: 'cool-agent' })
5557
t.deepEqual(rec.url, {

0 commit comments

Comments
 (0)