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
 | 
						|
}
 |