Skip to content

Commit e44e1ac

Browse files
committed
tests: add ecs-logging spec validation
This adds an ecs-logging spec validator (utils/lib/ecs-loggin-validate.js) and uses it everywhere the ECS JSON Schema-based validation was being done in tests.
1 parent 550ab1b commit e44e1ac

File tree

11 files changed

+440
-14
lines changed

11 files changed

+440
-14
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ check: check-license-headers
2020
(cd loggers/winston && npx standard)
2121
(cd loggers/morgan && npx standard)
2222
(cd loggers/pino && npx standard)
23+
(cd utils && npx standard)
2324

2425
.PHONY: fmt
2526
fmt:
2627
(cd helpers && npx standard --fix)
2728
(cd loggers/winston && npx standard --fix)
2829
(cd loggers/morgan && npx standard --fix)
2930
(cd loggers/pino && npx standard --fix)
31+
(cd utils && npx standard --fix)
3032

3133
.PHONY: test
3234
test:

helpers/test.js

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const Ajv = require('ajv').default
2424
const semver = require('semver')
2525
const test = require('tap').test
2626

27+
const { ecsLoggingValidate } = require('../utils/lib/ecs-logging-validate')
28+
2729
const {
2830
version,
2931
stringify,
@@ -40,7 +42,7 @@ addFormats(ajv)
4042
const validate = ajv.compile(require('../utils/schema.json'))
4143

4244
test('stringify should return a valid ecs json', t => {
43-
const ecs = {
45+
const ecsFields = {
4446
'@timestamp': new Date().toISOString(),
4547
'log.level': 'info',
4648
message: 'hello world',
@@ -49,23 +51,49 @@ test('stringify should return a valid ecs json', t => {
4951
}
5052
}
5153

52-
const line = JSON.parse(stringify(ecs))
53-
t.equal(validate(line), true)
54+
const line = stringify(ecsFields)
55+
const rec = JSON.parse(line)
56+
t.equal(validate(rec), true)
57+
t.equal(ecsLoggingValidate(line), null)
5458
t.end()
5559
})
5660

57-
test('Bad ecs json (on purpose)', t => {
58-
const ecs = {
61+
test('bad ECS json on purpose: @timestamp', t => {
62+
const ecsFields = {
5963
'@timestamp': 'not a date',
6064
'log.level': 'info',
61-
message: true,
65+
message: 'foo',
6266
ecs: {
6367
version: '1.4.0'
6468
}
6569
}
6670

67-
const line = JSON.parse(stringify(ecs))
68-
t.equal(validate(line), false)
71+
const line = stringify(ecsFields)
72+
const rec = JSON.parse(line)
73+
74+
t.equal(validate(rec), false)
75+
76+
const err = ecsLoggingValidate(line)
77+
t.ok(err, 'got an ecsLoggingValidate error')
78+
t.equal(err.details.length, 1)
79+
t.ok(err.details[0].message)
80+
t.equal(err.details[0].specKey, 'type')
81+
t.equal(err.details[0].name, '@timestamp')
82+
83+
t.end()
84+
})
85+
86+
test('bad ECS json on purpose: message type, ecs.version missing', t => {
87+
const line = '{"@timestamp":"2021-02-01T21:21:06.281Z","log.level":"info","message":true,"foo":"bar"}'
88+
89+
const err = ecsLoggingValidate(line)
90+
t.ok(err, 'got an ecsLoggingValidate error')
91+
t.equal(err.details.length, 2)
92+
t.equal(err.details[0].specKey, 'type')
93+
t.equal(err.details[0].name, 'message')
94+
t.equal(err.details[1].specKey, 'required')
95+
t.equal(err.details[1].name, 'ecs.version')
96+
6997
t.end()
7098
})
7199

@@ -121,6 +149,7 @@ test('formatHttpRequest and formatHttpResponse should return a valid ecs object'
121149

122150
const line = JSON.parse(stringify(ecs))
123151
t.ok(validate(line))
152+
t.equal(ecsLoggingValidate(line), null)
124153

125154
t.deepEqual(line.user_agent, { original: 'cool-agent' })
126155
t.deepEqual(line.url, {
@@ -171,6 +200,7 @@ test('stringify should emit valid tracing fields', t => {
171200

172201
const after = JSON.parse(stringify(before))
173202
t.ok(validate(after))
203+
t.equal(ecsLoggingValidate(after), null)
174204
t.deepEqual(after.trace, { id: '1' }, 'trace.id is stringified')
175205
t.deepEqual(after.transaction, { id: '2' }, 'transaction.id is stringified')
176206
t.deepEqual(after.span, { id: '3' },

loggers/morgan/test/apm.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const Ajv = require('ajv').default
2929
const split = require('split2')
3030
const test = require('tap').test
3131

32+
const { ecsLoggingValidate } = require('../../../utils/lib/ecs-logging-validate')
33+
3234
const ajv = new Ajv({
3335
allErrors: true,
3436
verbose: true
@@ -117,6 +119,7 @@ test('tracing integration works', t => {
117119
}
118120
if (logObj) {
119121
t.ok(validate(logObj), 'logObj is ECS valid')
122+
t.equal(ecsLoggingValidate(logObj), null)
120123
logObjs.push(logObj)
121124
}
122125
// Unlike the equivalent apm.test.js for other logging frameworks, we are

loggers/morgan/test/basic.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const split = require('split2')
2727
const test = require('tap').test
2828

2929
const ecsFormat = require('../')
30+
const { ecsLoggingValidate } = require('../../../utils/lib/ecs-logging-validate')
3031

3132
const ajv = new Ajv({
3233
allErrors: true,
@@ -69,10 +70,11 @@ function makeExpressServerAndRequest (logger, path, reqOpts, body, cb) {
6970
}
7071

7172
test('Should produce valid ecs logs', t => {
72-
t.plan(2)
73+
t.plan(3)
7374

7475
const stream = split(JSON.parse).on('data', line => {
7576
t.true(validate(line))
77+
t.equal(ecsLoggingValidate(line), null)
7678
})
7779
const logger = morgan(ecsFormat(), { stream })
7880

loggers/pino/test/apm.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const Ajv = require('ajv').default
2929
const split = require('split2')
3030
const test = require('tap').test
3131

32+
const { ecsLoggingValidate } = require('../../../utils/lib/ecs-logging-validate')
33+
3234
const ajv = new Ajv({
3335
allErrors: true,
3436
verbose: true
@@ -117,6 +119,7 @@ test('tracing integration works', t => {
117119
}
118120
if (logObj) {
119121
t.ok(validate(logObj), 'logObj is ECS valid')
122+
t.equal(ecsLoggingValidate(logObj), null)
120123
logObjs.push(logObj)
121124
}
122125
if (traceObjs.length >= 3 && logObjs.length >= 1) {

loggers/pino/test/basic.test.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const split = require('split2')
2828
const test = require('tap').test
2929

3030
const ecsFormat = require('../')
31+
const { ecsLoggingValidate } = require('../../../utils/lib/ecs-logging-validate')
3132

3233
const ajv = new Ajv({
3334
allErrors: true,
@@ -40,6 +41,7 @@ test('Should produce valid ecs logs', t => {
4041
const stream = split(JSON.parse).once('data', line => {
4142
t.deepEqual(line['log.level'], 'info')
4243
t.ok(validate(line))
44+
t.equal(ecsLoggingValidate(line), null)
4345
t.end()
4446
})
4547

@@ -51,6 +53,7 @@ test('Should map "name" to "log.logger"', t => {
5153
const stream = split(JSON.parse).once('data', line => {
5254
t.deepEqual(line.log, { logger: 'myName' })
5355
t.ok(validate(line))
56+
t.equal(ecsLoggingValidate(line), null)
5457
t.end()
5558
})
5659

@@ -63,6 +66,7 @@ test('Should append any additional property to the log message', t => {
6366
const stream = split(JSON.parse).once('data', line => {
6467
t.equal(line.foo, 'bar')
6568
t.ok(validate(line))
69+
t.equal(ecsLoggingValidate(line), null)
6670
t.end()
6771
})
6872

@@ -81,10 +85,11 @@ test('can log non-HTTP res & req fields', t => {
8185
})
8286

8387
test('convertReqRes:true and HTTP req, res', t => {
84-
t.plan(5)
88+
t.plan(7)
8589

8690
const stream = split(JSON.parse).on('data', rec => {
8791
t.ok(validate(rec))
92+
t.equal(ecsLoggingValidate(rec), null)
8893
if (rec.message === 'handled request') {
8994
// Spot check that some of the ECS HTTP and User agent fields are there.
9095
t.equal(rec.http.request.method, 'get', 'http.request.method')
@@ -130,6 +135,7 @@ test('convertErr is true by default', t => {
130135
log.info({ err: new Error('boom') }, 'hi')
131136
const rec = recs[0]
132137
t.ok(validate(rec))
138+
t.equal(ecsLoggingValidate(rec), null)
133139
t.equal(rec.error.type, 'Error')
134140
t.equal(rec.error.message, 'boom')
135141
t.match(rec.error.stack_trace, /^Error: boom\n {4}at/)
@@ -164,6 +170,7 @@ test('convertErr=false allows passing through err=<non-Error>', t => {
164170
log.info({ err: 42 }, 'hi')
165171
const rec = recs[0]
166172
t.ok(validate(rec))
173+
t.equal(ecsLoggingValidate(rec), null)
167174
t.equal(rec.err, 42, 'rec.err is unchanged')
168175
t.equal(rec.error, undefined, 'no rec.error is set')
169176
t.end()
@@ -180,6 +187,7 @@ test('createEcsPinoOptions with no formatters.log', t => {
180187
log.info({ err: 42, req: 'my req', res: null }, 'hi')
181188
const rec = recs[0]
182189
t.ok(validate(rec))
190+
t.equal(ecsLoggingValidate(rec), null)
183191
t.equal(rec.err, 42)
184192
t.equal(rec.req, 'my req')
185193
t.equal(rec.res, null)

loggers/winston/test/apm.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const Ajv = require('ajv').default
2929
const split = require('split2')
3030
const test = require('tap').test
3131

32+
const { ecsLoggingValidate } = require('../../../utils/lib/ecs-logging-validate')
33+
3234
const ajv = new Ajv({
3335
allErrors: true,
3436
verbose: true
@@ -117,6 +119,7 @@ test('tracing integration works', t => {
117119
}
118120
if (logObj) {
119121
t.ok(validate(logObj), 'logObj is ECS valid')
122+
t.equal(ecsLoggingValidate(logObj), null)
120123
logObjs.push(logObj)
121124
}
122125
if (traceObjs.length >= 3 && logObjs.length >= 1) {

loggers/winston/test/basic.test.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const Ajv = require('ajv').default
2727
const { version } = require('@elastic/ecs-helpers')
2828

2929
const ecsFormat = require('../')
30+
const { ecsLoggingValidate } = require('../../../utils/lib/ecs-logging-validate')
3031

3132
const ajv = new Ajv({
3233
allErrors: true,
@@ -53,7 +54,7 @@ class CaptureTransport extends Transport {
5354
}
5455

5556
test('Should produce valid ecs logs', t => {
56-
t.plan(2)
57+
t.plan(4)
5758

5859
const cap = new CaptureTransport()
5960
const logger = winston.createLogger({
@@ -64,13 +65,14 @@ test('Should produce valid ecs logs', t => {
6465
logger.error('ecs is cool!', { hello: 'world' })
6566

6667
cap.records.forEach((rec) => {
67-
t.true(validate(rec))
68+
t.ok(validate(rec))
69+
t.equal(ecsLoggingValidate(rec), null)
6870
})
6971
t.end()
7072
})
7173

7274
test('Bad ecs log (on purpose)', t => {
73-
t.plan(1)
75+
t.plan(2)
7476

7577
const cap = new CaptureTransport()
7678
const logger = winston.createLogger({
@@ -82,6 +84,7 @@ test('Bad ecs log (on purpose)', t => {
8284
cap.records.forEach((rec) => {
8385
rec['@timestamp'] = true // Intentionally break it
8486
t.false(validate(rec))
87+
t.notOk(ecsLoggingValidate(rec))
8588
})
8689
t.end()
8790
})
@@ -201,7 +204,9 @@ test('http request and response (req, res keys)', t => {
201204
res.on('close', function () {
202205
t.equal(cap.records.length, 2)
203206
t.ok(validate(cap.records[0]), 'record 0 is ECS valid')
207+
t.equal(ecsLoggingValidate(cap.records[0]), null)
204208
t.ok(validate(cap.records[1]), 'record 1 is ECS valid')
209+
t.equal(ecsLoggingValidate(cap.records[1]), null)
205210
// Spot check that some of the ECS HTTP fields are there.
206211
t.equal(cap.records[0].http.request.method, 'post',
207212
'http.request.method')
@@ -247,6 +252,7 @@ test('convertErr is true by default', t => {
247252
log.info('hi', { err: new Error('boom') })
248253
const rec = cap.records[0]
249254
t.ok(validate(rec))
255+
t.equal(ecsLoggingValidate(rec), null)
250256
t.equal(rec.error.type, 'Error')
251257
t.equal(rec.error.message, 'boom')
252258
t.match(rec.error.stack_trace, /^Error: boom\n {4}at/)
@@ -282,6 +288,7 @@ test('convertErr=false allows passing through err=<non-Error>', t => {
282288
log.info('hi', { err: 42 })
283289
const rec = cap.records[0]
284290
t.ok(validate(rec))
291+
t.equal(ecsLoggingValidate(rec), null)
285292
t.equal(rec.err, 42, 'rec.err is unchanged')
286293
t.equal(rec.error, undefined, 'no rec.error is set')
287294
t.end()

0 commit comments

Comments
 (0)