You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							438 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							438 lines
						
					
					
						
							12 KiB
						
					
					
				| 'use strict'
 | |
| 
 | |
| /* eslint no-prototype-builtins: 0 */
 | |
| 
 | |
| const format = require('quick-format-unescaped')
 | |
| const { mapHttpRequest, mapHttpResponse } = require('pino-std-serializers')
 | |
| const SonicBoom = require('sonic-boom')
 | |
| const stringifySafe = require('fast-safe-stringify')
 | |
| const {
 | |
|   lsCacheSym,
 | |
|   chindingsSym,
 | |
|   parsedChindingsSym,
 | |
|   writeSym,
 | |
|   serializersSym,
 | |
|   formatOptsSym,
 | |
|   endSym,
 | |
|   stringifiersSym,
 | |
|   stringifySym,
 | |
|   wildcardFirstSym,
 | |
|   needsMetadataGsym,
 | |
|   redactFmtSym,
 | |
|   streamSym,
 | |
|   nestedKeySym,
 | |
|   formattersSym,
 | |
|   messageKeySym
 | |
| } = require('./symbols')
 | |
| 
 | |
| function noop () {}
 | |
| 
 | |
| function genLog (level, hook) {
 | |
|   if (!hook) return LOG
 | |
| 
 | |
|   return function hookWrappedLog (...args) {
 | |
|     hook.call(this, args, LOG, level)
 | |
|   }
 | |
| 
 | |
|   function LOG (o, ...n) {
 | |
|     if (typeof o === 'object') {
 | |
|       let msg = o
 | |
|       if (o !== null) {
 | |
|         if (o.method && o.headers && o.socket) {
 | |
|           o = mapHttpRequest(o)
 | |
|         } else if (typeof o.setHeader === 'function') {
 | |
|           o = mapHttpResponse(o)
 | |
|         }
 | |
|       }
 | |
|       if (this[nestedKeySym]) o = { [this[nestedKeySym]]: o }
 | |
|       let formatParams
 | |
|       if (msg === null && n.length === 0) {
 | |
|         formatParams = [null]
 | |
|       } else {
 | |
|         msg = n.shift()
 | |
|         formatParams = n
 | |
|       }
 | |
|       this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level)
 | |
|     } else {
 | |
|       this[writeSym](null, format(o, n, this[formatOptsSym]), level)
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // magically escape strings for json
 | |
| // relying on their charCodeAt
 | |
| // everything below 32 needs JSON.stringify()
 | |
| // 34 and 92 happens all the time, so we
 | |
| // have a fast case for them
 | |
| function asString (str) {
 | |
|   let result = ''
 | |
|   let last = 0
 | |
|   let found = false
 | |
|   let point = 255
 | |
|   const l = str.length
 | |
|   if (l > 100) {
 | |
|     return JSON.stringify(str)
 | |
|   }
 | |
|   for (var i = 0; i < l && point >= 32; i++) {
 | |
|     point = str.charCodeAt(i)
 | |
|     if (point === 34 || point === 92) {
 | |
|       result += str.slice(last, i) + '\\'
 | |
|       last = i
 | |
|       found = true
 | |
|     }
 | |
|   }
 | |
|   if (!found) {
 | |
|     result = str
 | |
|   } else {
 | |
|     result += str.slice(last)
 | |
|   }
 | |
|   return point < 32 ? JSON.stringify(str) : '"' + result + '"'
 | |
| }
 | |
| 
 | |
| function asJson (obj, msg, num, time) {
 | |
|   const stringify = this[stringifySym]
 | |
|   const stringifiers = this[stringifiersSym]
 | |
|   const end = this[endSym]
 | |
|   const chindings = this[chindingsSym]
 | |
|   const serializers = this[serializersSym]
 | |
|   const formatters = this[formattersSym]
 | |
|   const messageKey = this[messageKeySym]
 | |
|   let data = this[lsCacheSym][num] + time
 | |
| 
 | |
|   // we need the child bindings added to the output first so instance logged
 | |
|   // objects can take precedence when JSON.parse-ing the resulting log line
 | |
|   data = data + chindings
 | |
| 
 | |
|   let value
 | |
|   const notHasOwnProperty = obj.hasOwnProperty === undefined
 | |
|   if (formatters.log) {
 | |
|     obj = formatters.log(obj)
 | |
|   }
 | |
|   if (msg !== undefined) {
 | |
|     obj[messageKey] = msg
 | |
|   }
 | |
|   const wildcardStringifier = stringifiers[wildcardFirstSym]
 | |
|   for (const key in obj) {
 | |
|     value = obj[key]
 | |
|     if ((notHasOwnProperty || obj.hasOwnProperty(key)) && value !== undefined) {
 | |
|       value = serializers[key] ? serializers[key](value) : value
 | |
| 
 | |
|       const stringifier = stringifiers[key] || wildcardStringifier
 | |
| 
 | |
|       switch (typeof value) {
 | |
|         case 'undefined':
 | |
|         case 'function':
 | |
|           continue
 | |
|         case 'number':
 | |
|           /* eslint no-fallthrough: "off" */
 | |
|           if (Number.isFinite(value) === false) {
 | |
|             value = null
 | |
|           }
 | |
|         // this case explicitly falls through to the next one
 | |
|         case 'boolean':
 | |
|           if (stringifier) value = stringifier(value)
 | |
|           break
 | |
|         case 'string':
 | |
|           value = (stringifier || asString)(value)
 | |
|           break
 | |
|         default:
 | |
|           value = (stringifier || stringify)(value)
 | |
|       }
 | |
|       if (value === undefined) continue
 | |
|       data += ',"' + key + '":' + value
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return data + end
 | |
| }
 | |
| 
 | |
| function asChindings (instance, bindings) {
 | |
|   let value
 | |
|   let data = instance[chindingsSym]
 | |
|   const stringify = instance[stringifySym]
 | |
|   const stringifiers = instance[stringifiersSym]
 | |
|   const wildcardStringifier = stringifiers[wildcardFirstSym]
 | |
|   const serializers = instance[serializersSym]
 | |
|   const formatter = instance[formattersSym].bindings
 | |
|   bindings = formatter(bindings)
 | |
| 
 | |
|   for (const key in bindings) {
 | |
|     value = bindings[key]
 | |
|     const valid = key !== 'level' &&
 | |
|       key !== 'serializers' &&
 | |
|       key !== 'formatters' &&
 | |
|       key !== 'customLevels' &&
 | |
|       bindings.hasOwnProperty(key) &&
 | |
|       value !== undefined
 | |
|     if (valid === true) {
 | |
|       value = serializers[key] ? serializers[key](value) : value
 | |
|       value = (stringifiers[key] || wildcardStringifier || stringify)(value)
 | |
|       if (value === undefined) continue
 | |
|       data += ',"' + key + '":' + value
 | |
|     }
 | |
|   }
 | |
|   return data
 | |
| }
 | |
| 
 | |
| function getPrettyStream (opts, prettifier, dest, instance) {
 | |
|   if (prettifier && typeof prettifier === 'function') {
 | |
|     prettifier = prettifier.bind(instance)
 | |
|     return prettifierMetaWrapper(prettifier(opts), dest, opts)
 | |
|   }
 | |
|   try {
 | |
|     const prettyFactory = require('pino-pretty').prettyFactory || require('pino-pretty')
 | |
|     prettyFactory.asMetaWrapper = prettifierMetaWrapper
 | |
|     return prettifierMetaWrapper(prettyFactory(opts), dest, opts)
 | |
|   } catch (e) {
 | |
|     if (e.message.startsWith("Cannot find module 'pino-pretty'")) {
 | |
|       throw Error('Missing `pino-pretty` module: `pino-pretty` must be installed separately')
 | |
|     };
 | |
|     throw e
 | |
|   }
 | |
| }
 | |
| 
 | |
| function prettifierMetaWrapper (pretty, dest, opts) {
 | |
|   opts = Object.assign({ suppressFlushSyncWarning: false }, opts)
 | |
|   let warned = false
 | |
|   return {
 | |
|     [needsMetadataGsym]: true,
 | |
|     lastLevel: 0,
 | |
|     lastMsg: null,
 | |
|     lastObj: null,
 | |
|     lastLogger: null,
 | |
|     flushSync () {
 | |
|       if (opts.suppressFlushSyncWarning || warned) {
 | |
|         return
 | |
|       }
 | |
|       warned = true
 | |
|       setMetadataProps(dest, this)
 | |
|       dest.write(pretty(Object.assign({
 | |
|         level: 40, // warn
 | |
|         msg: 'pino.final with prettyPrint does not support flushing',
 | |
|         time: Date.now()
 | |
|       }, this.chindings())))
 | |
|     },
 | |
|     chindings () {
 | |
|       const lastLogger = this.lastLogger
 | |
|       let chindings = null
 | |
| 
 | |
|       // protection against flushSync being called before logging
 | |
|       // anything
 | |
|       if (!lastLogger) {
 | |
|         return null
 | |
|       }
 | |
| 
 | |
|       if (lastLogger.hasOwnProperty(parsedChindingsSym)) {
 | |
|         chindings = lastLogger[parsedChindingsSym]
 | |
|       } else {
 | |
|         chindings = JSON.parse('{' + lastLogger[chindingsSym].substr(1) + '}')
 | |
|         lastLogger[parsedChindingsSym] = chindings
 | |
|       }
 | |
| 
 | |
|       return chindings
 | |
|     },
 | |
|     write (chunk) {
 | |
|       const lastLogger = this.lastLogger
 | |
|       const chindings = this.chindings()
 | |
| 
 | |
|       let time = this.lastTime
 | |
| 
 | |
|       if (time.match(/^\d+/)) {
 | |
|         time = parseInt(time)
 | |
|       } else {
 | |
|         time = time.slice(1, -1)
 | |
|       }
 | |
| 
 | |
|       const lastObj = this.lastObj
 | |
|       const lastMsg = this.lastMsg
 | |
|       const errorProps = null
 | |
| 
 | |
|       const formatters = lastLogger[formattersSym]
 | |
|       const formattedObj = formatters.log ? formatters.log(lastObj) : lastObj
 | |
| 
 | |
|       const messageKey = lastLogger[messageKeySym]
 | |
|       if (lastMsg && formattedObj && !formattedObj.hasOwnProperty(messageKey)) {
 | |
|         formattedObj[messageKey] = lastMsg
 | |
|       }
 | |
| 
 | |
|       const obj = Object.assign({
 | |
|         level: this.lastLevel,
 | |
|         time
 | |
|       }, formattedObj, errorProps)
 | |
| 
 | |
|       const serializers = lastLogger[serializersSym]
 | |
|       const keys = Object.keys(serializers)
 | |
| 
 | |
|       for (var i = 0; i < keys.length; i++) {
 | |
|         const key = keys[i]
 | |
|         if (obj[key] !== undefined) {
 | |
|           obj[key] = serializers[key](obj[key])
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       for (const key in chindings) {
 | |
|         if (!obj.hasOwnProperty(key)) {
 | |
|           obj[key] = chindings[key]
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       const stringifiers = lastLogger[stringifiersSym]
 | |
|       const redact = stringifiers[redactFmtSym]
 | |
| 
 | |
|       const formatted = pretty(typeof redact === 'function' ? redact(obj) : obj)
 | |
|       if (formatted === undefined) return
 | |
| 
 | |
|       setMetadataProps(dest, this)
 | |
|       dest.write(formatted)
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function hasBeenTampered (stream) {
 | |
|   return stream.write !== stream.constructor.prototype.write
 | |
| }
 | |
| 
 | |
| function buildSafeSonicBoom (opts) {
 | |
|   const stream = new SonicBoom(opts)
 | |
|   stream.on('error', filterBrokenPipe)
 | |
|   return stream
 | |
| 
 | |
|   function filterBrokenPipe (err) {
 | |
|     // TODO verify on Windows
 | |
|     if (err.code === 'EPIPE') {
 | |
|       // If we get EPIPE, we should stop logging here
 | |
|       // however we have no control to the consumer of
 | |
|       // SonicBoom, so we just overwrite the write method
 | |
|       stream.write = noop
 | |
|       stream.end = noop
 | |
|       stream.flushSync = noop
 | |
|       stream.destroy = noop
 | |
|       return
 | |
|     }
 | |
|     stream.removeListener('error', filterBrokenPipe)
 | |
|     stream.emit('error', err)
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createArgsNormalizer (defaultOptions) {
 | |
|   return function normalizeArgs (instance, opts = {}, stream) {
 | |
|     // support stream as a string
 | |
|     if (typeof opts === 'string') {
 | |
|       stream = buildSafeSonicBoom({ dest: opts, sync: true })
 | |
|       opts = {}
 | |
|     } else if (typeof stream === 'string') {
 | |
|       stream = buildSafeSonicBoom({ dest: stream, sync: true })
 | |
|     } else if (opts instanceof SonicBoom || opts.writable || opts._writableState) {
 | |
|       stream = opts
 | |
|       opts = null
 | |
|     }
 | |
|     opts = Object.assign({}, defaultOptions, opts)
 | |
|     if ('extreme' in opts) {
 | |
|       throw Error('The extreme option has been removed, use pino.destination({ sync: false }) instead')
 | |
|     }
 | |
|     if ('onTerminated' in opts) {
 | |
|       throw Error('The onTerminated option has been removed, use pino.final instead')
 | |
|     }
 | |
|     if ('changeLevelName' in opts) {
 | |
|       process.emitWarning(
 | |
|         'The changeLevelName option is deprecated and will be removed in v7. Use levelKey instead.',
 | |
|         { code: 'changeLevelName_deprecation' }
 | |
|       )
 | |
|       opts.levelKey = opts.changeLevelName
 | |
|       delete opts.changeLevelName
 | |
|     }
 | |
|     const { enabled, prettyPrint, prettifier, messageKey } = opts
 | |
|     if (enabled === false) opts.level = 'silent'
 | |
|     stream = stream || process.stdout
 | |
|     if (stream === process.stdout && stream.fd >= 0 && !hasBeenTampered(stream)) {
 | |
|       stream = buildSafeSonicBoom({ fd: stream.fd, sync: true })
 | |
|     }
 | |
|     if (prettyPrint) {
 | |
|       const prettyOpts = Object.assign({ messageKey }, prettyPrint)
 | |
|       stream = getPrettyStream(prettyOpts, prettifier, stream, instance)
 | |
|     }
 | |
|     return { opts, stream }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function final (logger, handler) {
 | |
|   if (typeof logger === 'undefined' || typeof logger.child !== 'function') {
 | |
|     throw Error('expected a pino logger instance')
 | |
|   }
 | |
|   const hasHandler = (typeof handler !== 'undefined')
 | |
|   if (hasHandler && typeof handler !== 'function') {
 | |
|     throw Error('if supplied, the handler parameter should be a function')
 | |
|   }
 | |
|   const stream = logger[streamSym]
 | |
|   if (typeof stream.flushSync !== 'function') {
 | |
|     throw Error('final requires a stream that has a flushSync method, such as pino.destination')
 | |
|   }
 | |
| 
 | |
|   const finalLogger = new Proxy(logger, {
 | |
|     get: (logger, key) => {
 | |
|       if (key in logger.levels.values) {
 | |
|         return (...args) => {
 | |
|           logger[key](...args)
 | |
|           stream.flushSync()
 | |
|         }
 | |
|       }
 | |
|       return logger[key]
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   if (!hasHandler) {
 | |
|     return finalLogger
 | |
|   }
 | |
| 
 | |
|   return (err = null, ...args) => {
 | |
|     try {
 | |
|       stream.flushSync()
 | |
|     } catch (e) {
 | |
|       // it's too late to wait for the stream to be ready
 | |
|       // because this is a final tick scenario.
 | |
|       // in practice there shouldn't be a situation where it isn't
 | |
|       // however, swallow the error just in case (and for easier testing)
 | |
|     }
 | |
|     return handler(err, finalLogger, ...args)
 | |
|   }
 | |
| }
 | |
| 
 | |
| function stringify (obj) {
 | |
|   try {
 | |
|     return JSON.stringify(obj)
 | |
|   } catch (_) {
 | |
|     return stringifySafe(obj)
 | |
|   }
 | |
| }
 | |
| 
 | |
| function buildFormatters (level, bindings, log) {
 | |
|   return {
 | |
|     level,
 | |
|     bindings,
 | |
|     log
 | |
|   }
 | |
| }
 | |
| 
 | |
| function setMetadataProps (dest, that) {
 | |
|   if (dest[needsMetadataGsym] === true) {
 | |
|     dest.lastLevel = that.lastLevel
 | |
|     dest.lastMsg = that.lastMsg
 | |
|     dest.lastObj = that.lastObj
 | |
|     dest.lastTime = that.lastTime
 | |
|     dest.lastLogger = that.lastLogger
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = {
 | |
|   noop,
 | |
|   buildSafeSonicBoom,
 | |
|   getPrettyStream,
 | |
|   asChindings,
 | |
|   asJson,
 | |
|   genLog,
 | |
|   createArgsNormalizer,
 | |
|   final,
 | |
|   stringify,
 | |
|   buildFormatters
 | |
| }
 |