Skip to content

Commit 5c754f8

Browse files
committed
tests: ensure we are testing field order ("index") in ecs-logging validation
ecsLoggingValidate can only validate the "index" spec field if given a log record *string*. This changes the tests to pass in a string rather than a preparsed object. This brought up that pino does not satisfy the "index" -- a known issue -- so support for explicitly skipping that part of validation was added.
1 parent bc21fca commit 5c754f8

File tree

4 files changed

+52
-52
lines changed

4 files changed

+52
-52
lines changed

loggers/morgan/test/basic.test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ function makeExpressServerAndRequest (logger, path, reqOpts, body, cb) {
7272
test('Should produce valid ecs logs', t => {
7373
t.plan(3)
7474

75-
const stream = split(JSON.parse).on('data', line => {
76-
t.true(validate(line))
75+
const stream = split().on('data', line => {
76+
const rec = JSON.parse(line)
77+
t.true(validate(rec))
7778
t.equal(ecsLoggingValidate(line), null)
7879
})
7980
const logger = morgan(ecsFormat(), { stream })

loggers/pino/test/basic.test.js

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ addFormats(ajv)
3838
const validate = ajv.compile(require('../../../utils/schema.json'))
3939

4040
test('Should produce valid ecs logs', t => {
41-
const stream = split(JSON.parse).once('data', line => {
42-
t.deepEqual(line['log.level'], 'info')
43-
t.ok(validate(line))
44-
t.equal(ecsLoggingValidate(line), null)
41+
const stream = split().once('data', line => {
42+
const rec = JSON.parse(line)
43+
t.deepEqual(rec['log.level'], 'info')
44+
t.ok(validate(rec))
45+
t.equal(ecsLoggingValidate(line, { ignoreIndex: true }), null)
4546
t.end()
4647
})
4748

@@ -50,10 +51,11 @@ test('Should produce valid ecs logs', t => {
5051
})
5152

5253
test('Should map "name" to "log.logger"', t => {
53-
const stream = split(JSON.parse).once('data', line => {
54-
t.deepEqual(line.log, { logger: 'myName' })
55-
t.ok(validate(line))
56-
t.equal(ecsLoggingValidate(line), null)
54+
const stream = split().once('data', line => {
55+
const rec = JSON.parse(line)
56+
t.deepEqual(rec.log, { logger: 'myName' })
57+
t.ok(validate(rec))
58+
t.equal(ecsLoggingValidate(line, { ignoreIndex: true }), null)
5759
t.end()
5860
})
5961

@@ -63,10 +65,11 @@ test('Should map "name" to "log.logger"', t => {
6365
})
6466

6567
test('Should append any additional property to the log message', t => {
66-
const stream = split(JSON.parse).once('data', line => {
67-
t.equal(line.foo, 'bar')
68-
t.ok(validate(line))
69-
t.equal(ecsLoggingValidate(line), null)
68+
const stream = split().once('data', line => {
69+
const rec = JSON.parse(line)
70+
t.equal(rec.foo, 'bar')
71+
t.ok(validate(rec))
72+
t.equal(ecsLoggingValidate(line, { ignoreIndex: true }), null)
7073
t.end()
7174
})
7275

@@ -87,9 +90,10 @@ test('can log non-HTTP res & req fields', t => {
8790
test('convertReqRes:true and HTTP req, res', t => {
8891
t.plan(7)
8992

90-
const stream = split(JSON.parse).on('data', rec => {
93+
const stream = split().on('data', line => {
94+
const rec = JSON.parse(line)
9195
t.ok(validate(rec))
92-
t.equal(ecsLoggingValidate(rec), null)
96+
t.equal(ecsLoggingValidate(line, { ignoreIndex: true }), null)
9397
if (rec.message === 'handled request') {
9498
// Spot check that some of the ECS HTTP and User agent fields are there.
9599
t.equal(rec.http.request.method, 'get', 'http.request.method')
@@ -128,14 +132,14 @@ test('convertReqRes:true and HTTP req, res', t => {
128132
})
129133

130134
test('convertErr is true by default', t => {
131-
const recs = []
132-
const stream = split(JSON.parse).on('data', rec => { recs.push(rec) })
135+
const lines = []
136+
const stream = split().on('data', line => { lines.push(line) })
133137
const log = pino({ ...ecsFormat() }, stream)
134138

135139
log.info({ err: new Error('boom') }, 'hi')
136-
const rec = recs[0]
140+
const rec = JSON.parse(lines[0])
137141
t.ok(validate(rec))
138-
t.equal(ecsLoggingValidate(rec), null)
142+
t.equal(ecsLoggingValidate(lines[0], { ignoreIndex: true }), null)
139143
t.equal(rec.error.type, 'Error')
140144
t.equal(rec.error.message, 'boom')
141145
t.match(rec.error.stack_trace, /^Error: boom\n {4}at/)
@@ -159,18 +163,18 @@ test('convertErr does not blow up on non-Errors', t => {
159163
})
160164

161165
test('convertErr=false allows passing through err=<non-Error>', t => {
162-
const recs = []
163-
const stream = split(JSON.parse).on('data', rec => { recs.push(rec) })
166+
const lines = []
167+
const stream = split().on('data', line => { lines.push(line) })
164168
// For *coverage* testing we also set `convertReqRes` to ensure
165169
// createEcsPinoOptions includes a `formatters.log` function.
166170
const log = pino(
167171
{ ...ecsFormat({ convertErr: false, convertReqRes: true }) },
168172
stream)
169173

170174
log.info({ err: 42 }, 'hi')
171-
const rec = recs[0]
175+
const rec = JSON.parse(lines[0])
172176
t.ok(validate(rec))
173-
t.equal(ecsLoggingValidate(rec), null)
177+
t.equal(ecsLoggingValidate(lines[0], { ignoreIndex: true }), null)
174178
t.equal(rec.err, 42, 'rec.err is unchanged')
175179
t.equal(rec.error, undefined, 'no rec.error is set')
176180
t.end()
@@ -180,14 +184,14 @@ test('createEcsPinoOptions with no formatters.log', t => {
180184
// There is a supposed fast path in createEcsPinoOptions where formatters.log
181185
// is excluded. Since convertErr is true by default, this case is rare.
182186
// For coverage testing, we concoct that case here.
183-
const recs = []
184-
const stream = split(JSON.parse).on('data', rec => { recs.push(rec) })
187+
const lines = []
188+
const stream = split().on('data', line => { lines.push(line) })
185189
const log = pino({ ...ecsFormat({ convertErr: false }) }, stream)
186190

187191
log.info({ err: 42, req: 'my req', res: null }, 'hi')
188-
const rec = recs[0]
192+
const rec = JSON.parse(lines[0])
189193
t.ok(validate(rec))
190-
t.equal(ecsLoggingValidate(rec), null)
194+
t.equal(ecsLoggingValidate(lines[0], { ignoreIndex: true }), null)
191195
t.equal(rec.err, 42)
192196
t.equal(rec.req, 'my req')
193197
t.equal(rec.res, null)

loggers/winston/test/basic.test.js

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ test('Should produce valid ecs logs', t => {
6666

6767
cap.records.forEach((rec) => {
6868
t.ok(validate(rec))
69-
t.equal(ecsLoggingValidate(rec), null)
69+
})
70+
cap.infos.forEach((info) => {
71+
t.equal(ecsLoggingValidate(info[MESSAGE]), null)
7072
})
7173
t.end()
7274
})
@@ -204,9 +206,9 @@ test('http request and response (req, res keys)', t => {
204206
res.on('close', function () {
205207
t.equal(cap.records.length, 2)
206208
t.ok(validate(cap.records[0]), 'record 0 is ECS valid')
207-
t.equal(ecsLoggingValidate(cap.records[0]), null)
209+
t.equal(ecsLoggingValidate(cap.infos[0][MESSAGE]), null)
208210
t.ok(validate(cap.records[1]), 'record 1 is ECS valid')
209-
t.equal(ecsLoggingValidate(cap.records[1]), null)
211+
t.equal(ecsLoggingValidate(cap.infos[1][MESSAGE]), null)
210212
// Spot check that some of the ECS HTTP fields are there.
211213
t.equal(cap.records[0].http.request.method, 'post',
212214
'http.request.method')
@@ -224,24 +226,6 @@ test('http request and response (req, res keys)', t => {
224226
})
225227
})
226228

227-
test('Keys order', t => {
228-
t.plan(2)
229-
230-
const cap = new CaptureTransport()
231-
const logger = winston.createLogger({
232-
format: ecsFormat(),
233-
transports: [cap]
234-
})
235-
logger.info('hi') // index 0
236-
logger.error('oh noes', { hello: 'world' }) // index 1
237-
238-
t.equal(cap.infos[0][MESSAGE],
239-
`{"@timestamp":"${cap.records[0]['@timestamp']}","log.level":"info","message":"hi","ecs":{"version":"${version}"}}`)
240-
t.equal(cap.infos[1][MESSAGE],
241-
`{"@timestamp":"${cap.records[1]['@timestamp']}","log.level":"error","message":"oh noes","ecs":{"version":"${version}"},"hello":"world"}`)
242-
t.end()
243-
})
244-
245229
test('convertErr is true by default', t => {
246230
const cap = new CaptureTransport()
247231
const log = winston.createLogger({
@@ -252,7 +236,7 @@ test('convertErr is true by default', t => {
252236
log.info('hi', { err: new Error('boom') })
253237
const rec = cap.records[0]
254238
t.ok(validate(rec))
255-
t.equal(ecsLoggingValidate(rec), null)
239+
t.equal(ecsLoggingValidate(cap.infos[0][MESSAGE]), null)
256240
t.equal(rec.error.type, 'Error')
257241
t.equal(rec.error.message, 'boom')
258242
t.match(rec.error.stack_trace, /^Error: boom\n {4}at/)
@@ -288,7 +272,7 @@ test('convertErr=false allows passing through err=<non-Error>', t => {
288272
log.info('hi', { err: 42 })
289273
const rec = cap.records[0]
290274
t.ok(validate(rec))
291-
t.equal(ecsLoggingValidate(rec), null)
275+
t.equal(ecsLoggingValidate(cap.infos[0][MESSAGE]), null)
292276
t.equal(rec.err, 42, 'rec.err is unchanged')
293277
t.equal(rec.error, undefined, 'no rec.error is set')
294278
t.end()

utils/lib/ecs-logging-validate.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,15 @@ function dottedLookup (obj, name) {
9797
// NOT maintain key order:
9898
// > Object.keys(JSON.parse('{"a":true,"1":true,"c":true,"b":true}'))
9999
// [ '1', 'a', 'c', 'b' ]
100-
function ecsLoggingValidate (rec) {
100+
//
101+
// - @param {String|Object} rec - The log record to validate. If an *object*
102+
// is given then validation of "index" cannot be done.
103+
// - @param {Object} opts - Optional. Options to control validation.
104+
// - {Boolean} opts.ignoreIndex - Set true to ignore "index" in validation.
105+
function ecsLoggingValidate (rec, opts) {
106+
opts = opts || {}
107+
const ignoreIndex = !!opts.ignoreIndex
108+
101109
let recObj
102110
let recStr = null
103111
if (typeof (rec) === 'string') {
@@ -113,6 +121,9 @@ function ecsLoggingValidate (rec) {
113121

114122
const details = []
115123
const addDetail = detail => {
124+
if (ignoreIndex && detail.specKey === 'index') {
125+
return
126+
}
116127
details.push(detail)
117128
}
118129
const spec = loadSpec()

0 commit comments

Comments
 (0)