UNPKG

4.98 kBJavaScriptView Raw
1'use strict'
2
3/**
4 * Code imported from `pino-http`
5 * Repo: https://github.com/pinojs/pino-http
6 * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
7 */
8
9const nullLogger = require('abstract-logging')
10const pino = require('pino')
11const { serializersSym } = pino.symbols
12const {
13 FST_ERR_LOG_INVALID_DESTINATION,
14 FST_ERR_LOG_INVALID_LOGGER
15} = require('./errors')
16
17function createPinoLogger (opts) {
18 if (opts.stream && opts.file) {
19 throw new FST_ERR_LOG_INVALID_DESTINATION()
20 } else if (opts.file) {
21 // we do not have stream
22 opts.stream = pino.destination(opts.file)
23 delete opts.file
24 }
25
26 const prevLogger = opts.logger
27 const prevGenReqId = opts.genReqId
28 let logger = null
29
30 if (prevLogger) {
31 opts.logger = undefined
32 opts.genReqId = undefined
33 // we need to tap into pino internals because in v5 it supports
34 // adding serializers in child loggers
35 if (prevLogger[serializersSym]) {
36 opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
37 }
38 logger = prevLogger.child({}, opts)
39 opts.logger = prevLogger
40 opts.genReqId = prevGenReqId
41 } else {
42 logger = pino(opts, opts.stream)
43 }
44
45 return logger
46}
47
48const serializers = {
49 req: function asReqValue (req) {
50 return {
51 method: req.method,
52 url: req.url,
53 version: req.headers && req.headers['accept-version'],
54 hostname: req.hostname,
55 remoteAddress: req.ip,
56 remotePort: req.socket ? req.socket.remotePort : undefined
57 }
58 },
59 err: pino.stdSerializers.err,
60 res: function asResValue (reply) {
61 return {
62 statusCode: reply.statusCode
63 }
64 }
65}
66
67function now () {
68 const ts = process.hrtime()
69 return (ts[0] * 1e3) + (ts[1] / 1e6)
70}
71
72function createLogger (options) {
73 if (!options.logger) {
74 const logger = nullLogger
75 logger.child = () => logger
76 return { logger, hasLogger: false }
77 }
78
79 if (validateLogger(options.logger)) {
80 const logger = createPinoLogger({
81 logger: options.logger,
82 serializers: Object.assign({}, serializers, options.logger.serializers)
83 })
84 return { logger, hasLogger: true }
85 }
86
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 })
97 }
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}
104
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 * @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing)
111 *
112 * @returns {boolean} `true` when the logger meets the requirements.
113 *
114 * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
115 * missing required methods.
116 */
117function validateLogger (logger, strict) {
118 const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
119 const missingMethods = logger
120 ? methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
121 : methods
122
123 if (!missingMethods.length) {
124 return true
125 } else if ((missingMethods.length === methods.length) && !strict) {
126 return false
127 } else {
128 throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
129 }
130}
131
132/**
133 * Utility for creating a child logger with the appropriate bindings, logger factory
134 * and validation.
135 * @param {object} context
136 * @param {import('../fastify').FastifyBaseLogger} logger
137 * @param {import('../fastify').RawRequestDefaultExpression<any>} req
138 * @param {string} reqId
139 * @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
140 */
141function createChildLogger (context, logger, req, reqId, loggerOpts) {
142 const loggerBindings = {
143 [context.requestIdLogLabel]: reqId
144 }
145 const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
146
147 // Optimization: bypass validation if the factory is our own default factory
148 if (context.childLoggerFactory !== defaultChildLoggerFactory) {
149 validateLogger(child, true) // throw if the child is not a valid logger
150 }
151
152 return child
153}
154
155/**
156 * @param {import('../fastify.js').FastifyBaseLogger} logger
157 * @param {import('../types/logger.js').Bindings} bindings
158 * @param {import('../types/logger.js').ChildLoggerOptions} opts
159 */
160function defaultChildLoggerFactory (logger, bindings, opts) {
161 return logger.child(bindings, opts)
162}
163
164module.exports = {
165 createLogger,
166 createChildLogger,
167 defaultChildLoggerFactory,
168 serializers,
169 now
170}