text: 'use strict'
  
  /* eslint no-prototype-builtins: 0 */
  
  const { EventEmitter } = require('events')
  const {
    lsCacheSym,
    levelValSym,
    setLevelSym,
    getLevelSym,
    chindingsSym,
    parsedChindingsSym,
    mixinSym,
    asJsonSym,
    writeSym,
    mixinMergeStrategySym,
    timeSym,
    timeSliceIndexSym,
    streamSym,
    serializersSym,
    formattersSym,
    useOnlyCustomLevelsSym,
    needsMetadataGsym,
    redactFmtSym,
    stringifySym,
    formatOptsSym,
    stringifiersSym
  } = require('./symbols')
  const {
    getLevel,
    setLevel,
    isLevelEnabled,
    mappings,
    initialLsCache,
    genLsCache,
    assertNoLevelCollisions
  } = require('./levels')
  const {
    asChindings,
    asJson,
    buildFormatters,
    stringify
  } = require('./tools')
  const {
    version
  } = require('./meta')
  const redaction = require('./redaction')
  
  // note: use of class is satirical
  // https://github.com/pinojs/pino/pull/433#pullrequestreview-127703127
  const constructor = class Pino {}
  const prototype = {
    constructor,
    child,
    bindings,
    setBindings,
    flush,
    isLevelEnabled,
    version,
    get level () { return this[getLevelSym]() },
    set level (lvl) { this[setLevelSym](lvl) },
    get levelVal () { return this[levelValSym] },
    set levelVal (n) { throw Error('levelVal is read-only') },
    [lsCacheSym]: initialLsCache,
    [writeSym]: write,
    [asJsonSym]: asJson,
    [getLevelSym]: getLevel,
    [setLevelSym]: setLevel
  }
  
  Object.setPrototypeOf(prototype, EventEmitter.prototype)
  
  // exporting and consuming the prototype object using factory pattern fixes scoping issues with getters when serializing
  module.exports = function () {
    return Object.create(prototype)
  }
  
  const resetChildingsFormatter = bindings => bindings
  function child (bindings, options) {
    if (!bindings) {
      throw Error('missing bindings for child Pino')
    }
    options = options || {} // default options to empty object
    const serializers = this[serializersSym]
    const formatters = this[formattersSym]
    const instance = Object.create(this)
  
    if (options.hasOwnProperty('serializers') === true) {
      instance[serializersSym] = Object.create(null)
  
      for (const k in serializers) {
        instance[serializersSym][k] = serializers[k]
      }
      const parentSymbols = Object.getOwnPropertySymbols(serializers)
      /* eslint no-var: off */
      for (var i = 0; i < parentSymbols.length; i++) {
        const ks = parentSymbols[i]
        instance[serializersSym][ks] = serializers[ks]
      }
  
      for (const bk in options.serializers) {
        instance[serializersSym][bk] = options.serializers[bk]
      }
      const bindingsSymbols = Object.getOwnPropertySymbols(options.serializers)
      for (var bi = 0; bi < bindingsSymbols.length; bi++) {
        const bks = bindingsSymbols[bi]
        instance[serializersSym][bks] = options.serializers[bks]
      }
    } else instance[serializersSym] = serializers
    if (options.hasOwnProperty('formatters')) {
      const { level, bindings: chindings, log } = options.formatters
      instance[formattersSym] = buildFormatters(
        level || formatters.level,
        chindings || resetChildingsFormatter,
        log || formatters.log
      )
    } else {
      instance[formattersSym] = buildFormatters(
        formatters.level,
        resetChildingsFormatter,
        formatters.log
      )
    }
    if (options.hasOwnProperty('customLevels') === true) {
      assertNoLevelCollisions(this.levels, options.customLevels)
      instance.levels = mappings(options.customLevels, instance[useOnlyCustomLevelsSym])
      genLsCache(instance)
    }
  
    // redact must place before asChindings and only replace if exist
    if ((typeof options.redact === 'object' && options.redact !== null) || Array.isArray(options.redact)) {
      instance.redact = options.redact // replace redact directly
      const stringifiers = redaction(instance.redact, stringify)
      const formatOpts = { stringify: stringifiers[redactFmtSym] }
      instance[stringifySym] = stringify
      instance[stringifiersSym] = stringifiers
      instance[formatOptsSym] = formatOpts
    }
  
    instance[chindingsSym] = asChindings(instance, bindings)
    const childLevel = options.level || this.level
    instance[setLevelSym](childLevel)
  
    return instance
  }
  
  function bindings () {
    const chindings = this[chindingsSym]
    const chindingsJson = `{${chindings.substr(1)}}` // at least contains ,"pid":7068,"hostname":"myMac"
    const bindingsFromJson = JSON.parse(chindingsJson)
    delete bindingsFromJson.pid
    delete bindingsFromJson.hostname
    return bindingsFromJson
  }
  
  function setBindings (newBindings) {
    const chindings = asChindings(this, newBindings)
    this[chindingsSym] = chindings
    delete this[parsedChindingsSym]
  }
  
  /**
   * Default strategy for creating `mergeObject` from arguments and the result from `mixin()`.
   * Fields from `mergeObject` have higher priority in this strategy.
   *
   * @param {Object} mergeObject The object a user has supplied to the logging function.
   * @param {Object} mixinObject The result of the `mixin` method.
   * @return {Object}
   */
  function defaultMixinMergeStrategy (mergeObject, mixinObject) {
    return Object.assign(mixinObject, mergeObject)
  }
  
  function write (_obj, msg, num) {
    const t = this[timeSym]()
    const mixin = this[mixinSym]
    const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy
    let obj
  
    if (_obj === undefined || _obj === null) {
      obj = {}
    } else if (_obj instanceof Error) {
      obj = { err: _obj }
      if (msg === undefined) {
        msg = _obj.message
      }
    } else {
      obj = _obj
      if (msg === undefined && _obj.err) {
        msg = _obj.err.message
      }
    }
  
    if (mixin) {
      obj = mixinMergeStrategy(obj, mixin(obj))
    }
  
    const s = this[asJsonSym](obj, msg, num, t)
  
    const stream = this[streamSym]
    if (stream[needsMetadataGsym] === true) {
      stream.lastLevel = num
      stream.lastObj = obj
      stream.lastMsg = msg
      stream.lastTime = t.slice(this[timeSliceIndexSym])
      stream.lastLogger = this // for child loggers
    }
    stream.write(s)
  }
  
  function noop () {}
  
  function flush () {
    const stream = this[streamSym]
    if ('flush' in stream) stream.flush(noop)
  }
  