Skip to content

Commit 9e92e4f

Browse files
authored
fix: logger validator throws (fastify#4520)
1 parent 2ba790f commit 9e92e4f

File tree

5 files changed

+94
-42
lines changed

5 files changed

+94
-42
lines changed

docs/Reference/Errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@ A callback for a hook timed out
281281

282282
The logger accepts either a `'stream'` or a `'file'` as the destination.
283283

284+
#### FST_ERR_LOG_INVALID_LOGGER
285+
<a id="FST_ERR_LOG_INVALID_LOGGER"></a>
286+
287+
The logger should have all these methods: `'info'`, `'error'`,
288+
`'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`.
289+
284290
#### FST_ERR_REP_INVALID_PAYLOAD_TYPE
285291
<a id="FST_ERR_REP_INVALID_PAYLOAD_TYPE"></a>
286292

lib/errors.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ const codes = {
177177
'Cannot specify both logger.stream and logger.file options'
178178
),
179179

180+
FST_ERR_LOG_INVALID_LOGGER: createError(
181+
'FST_ERR_LOG_INVALID_LOGGER',
182+
"Invalid logger object provided. The logger instance should have these functions(s): '%s'."
183+
),
184+
180185
/**
181186
* reply
182187
*/

lib/logger.js

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
const nullLogger = require('abstract-logging')
1010
const pino = require('pino')
1111
const { serializersSym } = pino.symbols
12-
const { FST_ERR_LOG_INVALID_DESTINATION } = require('./errors')
12+
const {
13+
FST_ERR_LOG_INVALID_DESTINATION,
14+
FST_ERR_LOG_INVALID_LOGGER
15+
} = require('./errors')
1316

14-
function createPinoLogger (opts, stream) {
15-
stream = stream || opts.stream
16-
delete opts.stream
17-
18-
if (stream && opts.file) {
17+
function createPinoLogger (opts) {
18+
if (opts.stream && opts.file) {
1919
throw new FST_ERR_LOG_INVALID_DESTINATION()
2020
} else if (opts.file) {
2121
// we do not have stream
22-
stream = pino.destination(opts.file)
22+
opts.stream = pino.destination(opts.file)
2323
delete opts.file
2424
}
2525

@@ -39,7 +39,7 @@ function createPinoLogger (opts, stream) {
3939
opts.logger = prevLogger
4040
opts.genReqId = prevGenReqId
4141
} else {
42-
logger = pino(opts, stream)
42+
logger = pino(opts, opts.stream)
4343
}
4444

4545
return logger
@@ -70,50 +70,60 @@ function now () {
7070
}
7171

7272
function createLogger (options) {
73-
if (isValidLogger(options.logger)) {
73+
if (!options.logger) {
74+
const logger = nullLogger
75+
logger.child = () => logger
76+
return { logger, hasLogger: false }
77+
}
78+
79+
if (validateLogger(options.logger)) {
7480
const logger = createPinoLogger({
7581
logger: options.logger,
7682
serializers: Object.assign({}, serializers, options.logger.serializers)
7783
})
7884
return { logger, hasLogger: true }
79-
} else if (!options.logger) {
80-
const logger = nullLogger
81-
logger.child = () => logger
82-
return { logger, hasLogger: false }
83-
} else {
84-
const localLoggerOptions = {}
85-
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
86-
Reflect.ownKeys(options.logger).forEach(prop => {
87-
Object.defineProperty(localLoggerOptions, prop, {
88-
value: options.logger[prop],
89-
writable: true,
90-
enumerable: true,
91-
configurable: true
92-
})
93-
})
94-
}
95-
localLoggerOptions.level = localLoggerOptions.level || 'info'
96-
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
97-
options.logger = localLoggerOptions
98-
const logger = createPinoLogger(options.logger)
99-
return { logger, hasLogger: true }
10085
}
101-
}
10286

103-
function isValidLogger (logger) {
104-
if (!logger) {
105-
return false
87+
const localLoggerOptions = {}
88+
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
89+
Reflect.ownKeys(options.logger).forEach(prop => {
90+
Object.defineProperty(localLoggerOptions, prop, {
91+
value: options.logger[prop],
92+
writable: true,
93+
enumerable: true,
94+
configurable: true
95+
})
96+
})
10697
}
98+
localLoggerOptions.level = localLoggerOptions.level || 'info'
99+
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
100+
options.logger = localLoggerOptions
101+
const logger = createPinoLogger(options.logger)
102+
return { logger, hasLogger: true }
103+
}
107104

108-
let result = true
105+
/**
106+
* Determines if a provided logger object meets the requirements
107+
* of a Fastify compatible logger.
108+
*
109+
* @param {object} logger Object to validate.
110+
*
111+
* @returns {boolean} `true` when the logger meets the requirements.
112+
*
113+
* @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
114+
* missing required methods.
115+
*/
116+
function validateLogger (logger) {
109117
const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
110-
for (let i = 0; i < methods.length; i += 1) {
111-
if (!logger[methods[i]] || typeof logger[methods[i]] !== 'function') {
112-
result = false
113-
break
114-
}
118+
const missingMethods = methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
119+
120+
if (!missingMethods.length) {
121+
return true
122+
} else if (missingMethods.length === methods.length) {
123+
return false
124+
} else {
125+
throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
115126
}
116-
return result
117127
}
118128

119129
module.exports = {

test/logger.test.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
22

33
const { test, teardown, before } = require('tap')
4-
const helper = require('./helper')
54
const http = require('http')
65
const stream = require('stream')
76
const split = require('split2')
@@ -13,6 +12,9 @@ const fs = require('fs')
1312
const sget = require('simple-get').concat
1413
const dns = require('dns')
1514

15+
const helper = require('./helper')
16+
const { FST_ERR_LOG_INVALID_LOGGER } = require('../lib/errors')
17+
1618
const files = []
1719
let count = 0
1820
let localhost
@@ -268,6 +270,34 @@ test('can use external logger instance with custom serializer', t => {
268270
})
269271
})
270272

273+
test('should throw in case the external logger provided does not have a child method', t => {
274+
t.plan(1)
275+
const loggerInstance = {
276+
info: console.info,
277+
error: console.error,
278+
debug: console.debug,
279+
fatal: console.error,
280+
warn: console.warn,
281+
trace: console.trace
282+
}
283+
284+
t.throws(
285+
() => Fastify({ logger: loggerInstance }),
286+
FST_ERR_LOG_INVALID_LOGGER,
287+
"Invalid logger object provided. The logger instance should have these functions(s): 'child'."
288+
)
289+
})
290+
291+
test('should throw in case a partially matching logger is provided', t => {
292+
t.plan(1)
293+
294+
t.throws(
295+
() => Fastify({ logger: console }),
296+
FST_ERR_LOG_INVALID_LOGGER,
297+
"Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'."
298+
)
299+
})
300+
271301
test('expose the logger', t => {
272302
t.plan(2)
273303
let fastify = null

types/errors.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type FastifyErrorCodes = Record<
2020
'FST_ERR_MISSING_MIDDLEWARE' |
2121
'FST_ERR_HOOK_TIMEOUT' |
2222
'FST_ERR_LOG_INVALID_DESTINATION' |
23+
'FST_ERR_LOG_INVALID_LOGGER' |
2324
'FST_ERR_REP_INVALID_PAYLOAD_TYPE' |
2425
'FST_ERR_REP_ALREADY_SENT' |
2526
'FST_ERR_REP_SENT_VALUE'|

0 commit comments

Comments
 (0)