diff --git a/Readme.md b/Readme.md
index 62b23002..988615c9 100644
--- a/Readme.md
+++ b/Readme.md
@@ -298,8 +298,8 @@ const prettifyQuery = value => {
 }
 ```
 
-Additionally, `customPrettifiers` can be used to format the `time`, `hostname`, `pid`, `name`, `caller` and `level`
-outputs:
+Additionally, `customPrettifiers` can be used to format the `time`, `hostname`,
+`pid`, `name`, `caller` and `level` outputs:
 
 ```js
 {
@@ -335,7 +335,8 @@ const levelPrettifier = logLevel => `LEVEL: ${levelColorize(logLevel)}`
 }
 ```
 
-`messageFormat` option allows you to customize the message output. A template `string` like this can define the format:
+`messageFormat` option allows you to customize the message output.
+A template `string` like this can define the format:
 
 ```js
 {
@@ -343,7 +344,8 @@ const levelPrettifier = logLevel => `LEVEL: ${levelColorize(logLevel)}`
 }
 ```
 
-In addition to this, if / end statement blocks can also be specified. Else statements and nested conditions are not supported.
+In addition to this, if / end statement blocks can also be specified.
+Else statements and nested conditions are not supported.
 
 ```js
 {
diff --git a/index.js b/index.js
index d3218264..141fc8f2 100644
--- a/index.js
+++ b/index.js
@@ -4,211 +4,132 @@ const { isColorSupported } = require('colorette')
 const pump = require('pump')
 const { Transform } = require('readable-stream')
 const abstractTransport = require('pino-abstract-transport')
-const sjs = require('secure-json-parse')
 const colors = require('./lib/colors')
-const { ERROR_LIKE_KEYS, MESSAGE_KEY, TIMESTAMP_KEY, LEVEL_KEY, LEVEL_NAMES } = require('./lib/constants')
 const {
-  isObject,
-  prettifyErrorLog,
-  prettifyLevel,
-  prettifyMessage,
-  prettifyMetadata,
-  prettifyObject,
-  prettifyTime,
+  ERROR_LIKE_KEYS,
+  LEVEL_KEY,
+  LEVEL_LABEL,
+  MESSAGE_KEY,
+  TIMESTAMP_KEY
+} = require('./lib/constants')
+const {
   buildSafeSonicBoom,
-  filterLog,
-  handleCustomLevelsOpts,
-  handleCustomLevelsNamesOpts
+  parseFactoryOptions
 } = require('./lib/utils')
-
-const jsonParser = input => {
-  try {
-    return { value: sjs.parse(input, { protoAction: 'remove' }) }
-  } catch (err) {
-    return { err }
-  }
-}
-
+const pretty = require('./lib/pretty')
+
+/**
+ * @typedef {object} PinoPrettyOptions
+ * @property {boolean} [colorize] Indicates if colors should be used when
+ * prettifying. The default will be determined by the terminal capabilities at
+ * run time.
+ * @property {boolean} [colorizeObjects=true] Apply coloring to rendered objects
+ * when coloring is enabled.
+ * @property {boolean} [crlf=false] End lines with `\r\n` instead of `\n`.
+ * @property {string|null} [customColors=null] A comma separated list of colors
+ * to use for specific level labels, e.g. `err:red,info:blue`.
+ * @property {string|null} [customLevels=null] A comma separated list of user
+ * defined level names and numbers, e.g. `err:99,info:1`.
+ * @property {CustomPrettifiers} [customPrettifiers={}] A set of prettifier
+ * functions to apply to keys defined in this object.
+ * @property {K_ERROR_LIKE_KEYS} [errorLikeObjectKeys] A list of string property
+ * names to consider as error objects.
+ * @property {string} [errorProps=''] A comma separated list of properties on
+ * error objects to include in the output.
+ * @property {boolean} [hideObject=false] When `true`, data objects will be
+ * omitted from the output (except for error objects).
+ * @property {string} [ignore='hostname'] A comma separated list of log keys
+ * to omit when outputting the prettified log information.
+ * @property {undefined|string} [include=undefined] A comma separated list of
+ * log keys to include in the prettified log information. Only the keys in this
+ * list will be included in the output.
+ * @property {boolean} [levelFirst=false] When true, the log level will be the
+ * first field in the prettified output.
+ * @property {string} [levelKey='level'] The key name in the log data that
+ * contains the level value for the log.
+ * @property {string} [levelLabel='levelLabel'] Token name to use in
+ * `messageFormat` to represent the name of the logged level.
+ * @property {null|MessageFormatString|MessageFormatFunction} [messageFormat=null]
+ * When a string, defines how the prettified line should be formatted according
+ * to defined tokens. When a function, a synchronous function that returns a
+ * formatted string.
+ * @property {string} [messageKey='msg'] Defines the key in incoming logs that
+ * contains the message of the log, if present.
+ * @property {undefined|string|number} [minimumLevel=undefined] The minimum
+ * level for logs that should be processed. Any logs below this level will
+ * be omitted.
+ * @property {object} [outputStream=process.stdout] The stream to write
+ * prettified log lines to.
+ * @property {boolean} [singleLine=false] When `true` any objects, except error
+ * objects, in the log data will be printed as a single line instead as multiple
+ * lines.
+ * @property {string} [timestampKey='time'] Defines the key in incoming logs
+ * that contains the timestamp of the log, if present.
+ * @property {boolean|string} [translateTime=true] When true, will translate a
+ * JavaScript date integer into a human-readable string. If set to a string,
+ * it must be a format string.
+ * @property {boolean} [useOnlyCustomProps=true] When true, only custom levels
+ * and colors will be used if they have been provided.
+ */
+
+/**
+ * The default options that will be used when prettifying log lines.
+ *
+ * @type {PinoPrettyOptions}
+ */
 const defaultOptions = {
   colorize: isColorSupported,
   colorizeObjects: true,
   crlf: false,
+  customColors: null,
+  customLevels: null,
+  customPrettifiers: {},
   errorLikeObjectKeys: ERROR_LIKE_KEYS,
   errorProps: '',
-  customLevels: null,
-  customColors: null,
-  useOnlyCustomProps: true,
+  hideObject: false,
+  ignore: 'hostname',
+  include: undefined,
   levelFirst: false,
-  messageKey: MESSAGE_KEY,
+  levelKey: LEVEL_KEY,
+  levelLabel: LEVEL_LABEL,
   messageFormat: null,
+  messageKey: MESSAGE_KEY,
+  minimumLevel: undefined,
+  outputStream: process.stdout,
+  singleLine: false,
   timestampKey: TIMESTAMP_KEY,
   translateTime: true,
-  useMetadata: false,
-  outputStream: process.stdout,
-  customPrettifiers: {},
-  hideObject: false,
-  ignore: 'hostname',
-  include: undefined,
-  singleLine: false
+  useOnlyCustomProps: true
 }
 
+/**
+ * Processes the supplied options and returns a function that accepts log data
+ * and produces a prettified log string.
+ *
+ * @param {PinoPrettyOptions} options Configuration for the prettifier.
+ * @returns {LogPrettifierFunc}
+ */
 function prettyFactory (options) {
-  const opts = Object.assign({}, defaultOptions, options)
-  const EOL = opts.crlf ? '\r\n' : '\n'
-  const IDENT = '    '
-  const messageKey = opts.messageKey
-  const levelKey = opts.levelKey
-  const levelLabel = opts.levelLabel
-  const minimumLevel = opts.minimumLevel
-  const messageFormat = opts.messageFormat
-  const timestampKey = opts.timestampKey
-  const errorLikeObjectKeys = opts.errorLikeObjectKeys
-  const errorProps = opts.errorProps.split(',')
-  const useOnlyCustomProps = typeof opts.useOnlyCustomProps === 'boolean' ? opts.useOnlyCustomProps : opts.useOnlyCustomProps === 'true'
-  const customLevels = handleCustomLevelsOpts(opts.customLevels)
-  const customLevelNames = handleCustomLevelsNamesOpts(opts.customLevels)
-
-  const customColors = opts.customColors
-    ? opts.customColors
-      .split(',')
-      .reduce((agg, value) => {
-        const [level, color] = value.split(':')
-
-        const condition = useOnlyCustomProps ? opts.customLevels : customLevelNames[level] !== undefined
-        const levelNum = condition ? customLevelNames[level] : LEVEL_NAMES[level]
-        const colorIdx = levelNum !== undefined ? levelNum : level
-
-        agg.push([colorIdx, color])
-
-        return agg
-      }, [])
-    : undefined
-  const customProps = {
-    customLevels,
-    customLevelNames
-  }
-  if (useOnlyCustomProps && !opts.customLevels) {
-    customProps.customLevels = undefined
-    customProps.customLevelNames = undefined
-  }
-  const customPrettifiers = opts.customPrettifiers
-  const includeKeys = opts.include !== undefined ? new Set(opts.include.split(',')) : undefined
-  const ignoreKeys = (!includeKeys && opts.ignore) ? new Set(opts.ignore.split(',')) : undefined
-  const hideObject = opts.hideObject
-  const singleLine = opts.singleLine
-  const colorizer = colors(opts.colorize, customColors, useOnlyCustomProps)
-  const objectColorizer = opts.colorizeObjects ? colorizer : colors(false, [], false)
-
-  return pretty
-
-  function pretty (inputData) {
-    let log
-    if (!isObject(inputData)) {
-      const parsed = jsonParser(inputData)
-      if (parsed.err || !isObject(parsed.value)) {
-        // pass through
-        return inputData + EOL
-      }
-      log = parsed.value
-    } else {
-      log = inputData
-    }
-
-    if (minimumLevel) {
-      const condition = useOnlyCustomProps ? opts.customLevels : customLevelNames[minimumLevel] !== undefined
-      const minimum = (condition ? customLevelNames[minimumLevel] : LEVEL_NAMES[minimumLevel]) || Number(minimumLevel)
-      const level = log[levelKey === undefined ? LEVEL_KEY : levelKey]
-      if (level < minimum) return
-    }
-
-    const prettifiedMessage = prettifyMessage({ log, messageKey, colorizer, messageFormat, levelLabel, ...customProps, useOnlyCustomProps })
-
-    if (ignoreKeys || includeKeys) {
-      log = filterLog({ log, ignoreKeys, includeKeys })
-    }
-
-    const prettifiedLevel = prettifyLevel({ log, colorizer, levelKey, prettifier: customPrettifiers.level, ...customProps })
-    const prettifiedMetadata = prettifyMetadata({ log, prettifiers: customPrettifiers })
-    const prettifiedTime = prettifyTime({ log, translateFormat: opts.translateTime, timestampKey, prettifier: customPrettifiers.time })
-
-    let line = ''
-    if (opts.levelFirst && prettifiedLevel) {
-      line = `${prettifiedLevel}`
-    }
-
-    if (prettifiedTime && line === '') {
-      line = `${prettifiedTime}`
-    } else if (prettifiedTime) {
-      line = `${line} ${prettifiedTime}`
-    }
-
-    if (!opts.levelFirst && prettifiedLevel) {
-      if (line.length > 0) {
-        line = `${line} ${prettifiedLevel}`
-      } else {
-        line = prettifiedLevel
-      }
-    }
-
-    if (prettifiedMetadata) {
-      if (line.length > 0) {
-        line = `${line} ${prettifiedMetadata}:`
-      } else {
-        line = prettifiedMetadata
-      }
-    }
-
-    if (line.endsWith(':') === false && line !== '') {
-      line += ':'
-    }
-
-    if (prettifiedMessage !== undefined) {
-      if (line.length > 0) {
-        line = `${line} ${prettifiedMessage}`
-      } else {
-        line = prettifiedMessage
-      }
-    }
-
-    if (line.length > 0 && !singleLine) {
-      line += EOL
-    }
-
-    // pino@7+ does not log this anymore
-    if (log.type === 'Error' && log.stack) {
-      const prettifiedErrorLog = prettifyErrorLog({
-        log,
-        errorLikeKeys: errorLikeObjectKeys,
-        errorProperties: errorProps,
-        ident: IDENT,
-        eol: EOL
-      })
-      if (singleLine) line += EOL
-      line += prettifiedErrorLog
-    } else if (!hideObject) {
-      const skipKeys = [messageKey, levelKey, timestampKey].filter(key => typeof log[key] === 'string' || typeof log[key] === 'number' || typeof log[key] === 'boolean')
-      const prettifiedObject = prettifyObject({
-        input: log,
-        skipKeys,
-        customPrettifiers,
-        errorLikeKeys: errorLikeObjectKeys,
-        eol: EOL,
-        ident: IDENT,
-        singleLine,
-        colorizer: objectColorizer
-      })
-
-      // In single line mode, include a space only if prettified version isn't empty
-      if (singleLine && !/^\s$/.test(prettifiedObject)) {
-        line += ' '
-      }
-      line += prettifiedObject
-    }
-
-    return line
-  }
+  const context = parseFactoryOptions(Object.assign({}, defaultOptions, options))
+  return pretty.bind({ ...context, context })
 }
 
+/**
+ * @typedef {PinoPrettyOptions} BuildStreamOpts
+ * @property {object|number|string} [destination] A destination stream, file
+ * descriptor, or target path to a file.
+ * @property {boolean} [append]
+ * @property {boolean} [mkdir]
+ * @property {boolean} [sync=false]
+ */
+
+/**
+ * Constructs a {@link LogPrettifierFunc} and a stream to which the produced
+ * prettified log data will be written.
+ *
+ * @param {BuildStreamOpts} opts
+ * @returns {Transform | (Transform & OnUnknown)}
+ */
 function build (opts = {}) {
   const pretty = prettyFactory(opts)
   return abstractTransport(function (source) {
diff --git a/lib/colors.js b/lib/colors.js
index f6a20b15..5bb38042 100644
--- a/lib/colors.js
+++ b/lib/colors.js
@@ -94,16 +94,31 @@ function customColoredColorizerFactory (customColors, useOnlyCustomProps) {
   return customColoredColorizer
 }
 
+/**
+ * Applies colorization, if possible, to a string representing the passed in
+ * `level`. For example, the default colorizer will return a "green" colored
+ * string for the "info" level.
+ *
+ * @typedef {function} ColorizerFunc
+ * @param {string|number} level In either case, the input will map to a color
+ * for the specified level or to the color for `USERLVL` if the level is not
+ * recognized.
+ * @property {function} message Accepts one string parameter that will be
+ * colorized to a predefined color.
+ */
+
 /**
  * Factory function get a function to colorized levels. The returned function
  * also includes a `.message(str)` method to colorize strings.
  *
  * @param {boolean} [useColors=false] When `true` a function that applies standard
  * terminal colors is returned.
- * @param {array[]} [customColors] Touple where first item of each array is the level index and the second item is the color
- * @param {boolean} [useOnlyCustomProps] When `true`, only use the provided custom colors provided and not fallback to default
+ * @param {array[]} [customColors] Tuple where first item of each array is the
+ * level index and the second item is the color
+ * @param {boolean} [useOnlyCustomProps] When `true`, only use the provided
+ * custom colors provided and not fallback to default
  *
- * @returns {function} `function (level) {}` has a `.message(str)` method to
+ * @returns {ColorizerFunc} `function (level) {}` has a `.message(str)` method to
  * apply colorization to a string. The core function accepts either an integer
  * `level` or a `string` level. The integer level will map to a known level
  * string or to `USERLVL` if not known.  The string `level` will map to the same
diff --git a/lib/pretty.js b/lib/pretty.js
new file mode 100644
index 00000000..ebb43800
--- /dev/null
+++ b/lib/pretty.js
@@ -0,0 +1,169 @@
+'use strict'
+
+module.exports = pretty
+
+const sjs = require('secure-json-parse')
+
+const isObject = require('./utils/is-object')
+const prettifyErrorLog = require('./utils/prettify-error-log')
+const prettifyLevel = require('./utils/prettify-level')
+const prettifyMessage = require('./utils/prettify-message')
+const prettifyMetadata = require('./utils/prettify-metadata')
+const prettifyObject = require('./utils/prettify-object')
+const prettifyTime = require('./utils/prettify-time')
+const filterLog = require('./utils/filter-log')
+
+const {
+  LEVELS,
+  LEVEL_KEY,
+  LEVEL_NAMES
+} = require('./constants')
+
+const jsonParser = input => {
+  try {
+    return { value: sjs.parse(input, { protoAction: 'remove' }) }
+  } catch (err) {
+    return { err }
+  }
+}
+
+/**
+ * Orchestrates processing the received log data according to the provided
+ * configuration and returns a prettified log string.
+ *
+ * @typedef {function} LogPrettifierFunc
+ * @param {string|object} inputData A log string or a log-like object.
+ * @returns {string} A string that represents the prettified log data.
+ */
+function pretty (inputData) {
+  let log
+  if (!isObject(inputData)) {
+    const parsed = jsonParser(inputData)
+    if (parsed.err || !isObject(parsed.value)) {
+      // pass through
+      return inputData + this.EOL
+    }
+    log = parsed.value
+  } else {
+    log = inputData
+  }
+
+  if (this.minimumLevel) {
+    // We need to figure out if the custom levels has the desired minimum
+    // level & use that one if found. If not, determine if the level exists
+    // in the standard levels. In both cases, make sure we have the level
+    // number instead of the level name.
+    let condition
+    if (this.useOnlyCustomProps) {
+      condition = this.customLevels
+    } else {
+      condition = this.customLevelNames[this.minimumLevel] !== undefined
+    }
+    let minimum
+    if (condition) {
+      minimum = this.customLevelNames[this.minimumLevel]
+    } else {
+      minimum = LEVEL_NAMES[this.minimumLevel]
+    }
+    if (!minimum) {
+      minimum = typeof this.minimumLevel === 'string'
+        ? LEVEL_NAMES[this.minimumLevel]
+        : LEVEL_NAMES[LEVELS[this.minimumLevel].toLowerCase()]
+    }
+
+    const level = log[this.levelKey === undefined ? LEVEL_KEY : this.levelKey]
+    if (level < minimum) return
+  }
+
+  const prettifiedMessage = prettifyMessage({ log, context: this.context })
+
+  if (this.ignoreKeys || this.includeKeys) {
+    log = filterLog({ log, context: this.context })
+  }
+
+  const prettifiedLevel = prettifyLevel({
+    log,
+    context: {
+      ...this.context,
+      // This is odd. The colorizer ends up relying on the value of
+      // `customProperties` instead of the original `customLevels` and
+      // `customLevelNames`.
+      ...this.context.customProperties
+    }
+  })
+  const prettifiedMetadata = prettifyMetadata({ log, context: this.context })
+  const prettifiedTime = prettifyTime({ log, context: this.context })
+
+  let line = ''
+  if (this.levelFirst && prettifiedLevel) {
+    line = `${prettifiedLevel}`
+  }
+
+  if (prettifiedTime && line === '') {
+    line = `${prettifiedTime}`
+  } else if (prettifiedTime) {
+    line = `${line} ${prettifiedTime}`
+  }
+
+  if (!this.levelFirst && prettifiedLevel) {
+    if (line.length > 0) {
+      line = `${line} ${prettifiedLevel}`
+    } else {
+      line = prettifiedLevel
+    }
+  }
+
+  if (prettifiedMetadata) {
+    if (line.length > 0) {
+      line = `${line} ${prettifiedMetadata}:`
+    } else {
+      line = prettifiedMetadata
+    }
+  }
+
+  if (line.endsWith(':') === false && line !== '') {
+    line += ':'
+  }
+
+  if (prettifiedMessage !== undefined) {
+    if (line.length > 0) {
+      line = `${line} ${prettifiedMessage}`
+    } else {
+      line = prettifiedMessage
+    }
+  }
+
+  if (line.length > 0 && !this.singleLine) {
+    line += this.EOL
+  }
+
+  // pino@7+ does not log this anymore
+  if (log.type === 'Error' && log.stack) {
+    const prettifiedErrorLog = prettifyErrorLog({ log, context: this.context })
+    if (this.singleLine) line += this.EOL
+    line += prettifiedErrorLog
+  } else if (this.hideObject === false) {
+    const skipKeys = [
+      this.messageKey,
+      this.levelKey,
+      this.timestampKey
+    ].filter(key => {
+      return typeof log[key] === 'string' ||
+        typeof log[key] === 'number' ||
+        typeof log[key] === 'boolean'
+    })
+    const prettifiedObject = prettifyObject({
+      log,
+      skipKeys,
+      context: this.context
+    })
+
+    // In single line mode, include a space only if prettified version isn't empty
+    if (this.singleLine && !/^\s$/.test(prettifiedObject)) {
+      line += ' '
+    }
+    line += prettifiedObject
+  }
+
+  return line
+}
diff --git a/lib/utils/filter-log.js b/lib/utils/filter-log.js
index 9f5cec6c..66c1b354 100644
--- a/lib/utils/filter-log.js
+++ b/lib/utils/filter-log.js
@@ -7,23 +7,26 @@ const fastCopy = createCopier({})
 
 const deleteLogProperty = require('./delete-log-property')
 
+/**
+ * @typedef {object} FilterLogParams
+ * @property {object} log The log object to be modified.
+ * @property {PrettyContext} context The context object built from parsing
+ * the options.
+ */
+
 /**
  * Filter a log object by removing or including keys accordingly.
  * When `includeKeys` is passed, `ignoredKeys` will be ignored.
  * One of ignoreKeys or includeKeys must be pass in.
  *
- * @param {object} input
- * @param {object} input.log The log object to be modified.
- * @param {Set<string> | Array<string> | undefined} input.ignoreKeys
- *  An array of strings identifying the properties to be removed.
- * @param {Set<string> | Array<string> | undefined} input.includeKeys
- *  An array of strings identifying the properties to be included.
+ * @param {FilterLogParams} input
  *
  * @returns {object} A new `log` object instance that
  *  either only includes the keys in ignoreKeys
  *  or does not include those in ignoredKeys.
  */
-function filterLog ({ log, ignoreKeys, includeKeys }) {
+function filterLog ({ log, context }) {
+  const { ignoreKeys, includeKeys } = context
   const logCopy = fastCopy(log)
 
   if (includeKeys) {
diff --git a/lib/utils/filter-log.test.js b/lib/utils/filter-log.test.js
index 3ba54298..2099661a 100644
--- a/lib/utils/filter-log.test.js
+++ b/lib/utils/filter-log.test.js
@@ -3,6 +3,10 @@
 const tap = require('tap')
 const filterLog = require('./filter-log')
 
+const context = {
+  includeKeys: undefined,
+  ignoreKeys: undefined
+}
 const logData = {
   level: 30,
   time: 1522431328992,
@@ -20,27 +24,57 @@ const logData2 = Object.assign({
 
 tap.test('#filterLog with an ignoreKeys option', t => {
   t.test('filterLog removes single entry', async t => {
-    const result = filterLog({ log: logData, ignoreKeys: ['data1.data2.data-3'] })
+    const result = filterLog({
+      log: logData,
+      context: {
+        ...context,
+        ignoreKeys: ['data1.data2.data-3']
+      }
+    })
     t.same(result, { level: 30, time: 1522431328992, data1: { data2: { }, error: new Error('test') } })
   })
 
   t.test('filterLog removes multiple entries', async t => {
-    const result = filterLog({ log: logData, ignoreKeys: ['time', 'data1'] })
+    const result = filterLog({
+      log: logData,
+      context: {
+        ...context,
+        ignoreKeys: ['time', 'data1']
+      }
+    })
     t.same(result, { level: 30 })
   })
 
   t.test('filterLog keeps error instance', async t => {
-    const result = filterLog({ log: logData, ignoreKeys: [] })
+    const result = filterLog({
+      log: logData,
+      context: {
+        ...context,
+        ignoreKeys: []
+      }
+    })
     t.equal(logData.data1.error, result.data1.error)
   })
 
   t.test('filterLog removes entry with escape sequence', async t => {
-    const result = filterLog({ log: logData2, ignoreKeys: ['data1', 'logging\\.domain\\.corp/operation'] })
+    const result = filterLog({
+      log: logData2,
+      context: {
+        ...context,
+        ignoreKeys: ['data1', 'logging\\.domain\\.corp/operation']
+      }
+    })
     t.same(result, { level: 30, time: 1522431328992 })
   })
 
   t.test('filterLog removes entry with escape sequence nested', async t => {
-    const result = filterLog({ log: logData2, ignoreKeys: ['data1', 'logging\\.domain\\.corp/operation.producer'] })
+    const result = filterLog({
+      log: logData2,
+      context: {
+        ...context,
+        ignoreKeys: ['data1', 'logging\\.domain\\.corp/operation.producer']
+      }
+    })
     t.same(result, { level: 30, time: 1522431328992, 'logging.domain.corp/operation': { id: 'foo' } })
   })
 
@@ -55,17 +89,38 @@ const ignoreKeysArray = [
 ignoreKeysArray.forEach(ignoreKeys => {
   tap.test(`#filterLog with an includeKeys option when the ignoreKeys being ${ignoreKeys}`, t => {
     t.test('filterLog include nothing', async t => {
-      const result = filterLog({ log: logData, ignoreKeys, includeKeys: [] })
+      const result = filterLog({
+        log: logData,
+        context: {
+          ...context,
+          ignoreKeys,
+          includeKeys: []
+        }
+      })
       t.same(result, {})
     })
 
     t.test('filterLog include single entry', async t => {
-      const result = filterLog({ log: logData, ignoreKeys, includeKeys: ['time'] })
+      const result = filterLog({
+        log: logData,
+        context: {
+          ...context,
+          ignoreKeys,
+          includeKeys: ['time']
+        }
+      })
       t.same(result, { time: 1522431328992 })
     })
 
     t.test('filterLog include multiple entries', async t => {
-      const result = filterLog({ log: logData, ignoreKeys, includeKeys: ['time', 'data1'] })
+      const result = filterLog({
+        log: logData,
+        context: {
+          ...context,
+          ignoreKeys,
+          includeKeys: ['time', 'data1']
+        }
+      })
       t.same(result, {
         time: 1522431328992,
         data1: {
@@ -88,7 +143,13 @@ tap.test('#filterLog with circular references', t => {
   logData.circular = logData
 
   t.test('filterLog removes single entry', async t => {
-    const result = filterLog({ log: logData, ignoreKeys: ['data1'] })
+    const result = filterLog({
+      log: logData,
+      context: {
+        ...context,
+        ignoreKeys: ['data1']
+      }
+    })
 
     t.same(result.circular.level, result.level)
     t.same(result.circular.time, result.time)
@@ -98,13 +159,25 @@ tap.test('#filterLog with circular references', t => {
   })
 
   t.test('filterLog includes single entry', async t => {
-    const result = filterLog({ log: logData, includeKeys: ['data1'] })
+    const result = filterLog({
+      log: logData,
+      context: {
+        ...context,
+        includeKeys: ['data1']
+      }
+    })
 
     t.same(result, { data1: 'test' })
   })
 
   t.test('filterLog includes circular keys', async t => {
-    const result = filterLog({ log: logData, includeKeys: ['level', 'circular'] })
+    const result = filterLog({
+      log: logData,
+      context: {
+        ...context,
+        includeKeys: ['level', 'circular']
+      }
+    })
 
     t.same(result.circular.level, logData.level)
     t.same(result.circular.time, logData.time)
diff --git a/lib/utils/index.js b/lib/utils/index.js
index bcd0b54c..c4ff4c15 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -15,3 +15,76 @@ for (const file of files) {
 
   module.exports[snakeName] = require(`./${kebabName}.js`)
 }
+
+// The remainder of this file consists of jsdoc blocks that are difficult to
+// determine a more appropriate "home" for. As an example, the blocks associated
+// with custom prettifiers could live in either the `prettify-level`,
+// `prettify-metadata`, or `prettify-time` files since they are the primary
+// files where such code is used. But we want a central place to define common
+// doc blocks, so we are picking this file as the answer.
+
+/**
+ * A hash of log property names mapped to prettifier functions. When the
+ * incoming log data is being processed for prettification, any key on the log
+ * that matches a key in a custom prettifiers hash will be prettified using
+ * that matching custom prettifier. The value passed to the custom prettifier
+ * will the value associated with the corresponding log key.
+ *
+ * The hash may contain any arbitrary keys for arbitrary log properties, but it
+ * may also contain a set of predefined key names that map to well-known log
+ * properties. These keys are:
+ *
+ * + `time` (for the timestamp field)
+ * + `level` (for the level label field; value may be a level number instead
+ * of a level label)
+ * + `hostname`
+ * + `pid`
+ * + `name`
+ * + `caller`
+ *
+ * @typedef {Object.<string, CustomPrettifierFunc>} CustomPrettifiers
+ */
+
+/**
+ * A synchronous function to be used for prettifying a log property. It must
+ * return a string.
+ *
+ * @typedef {function} CustomPrettifierFunc
+ * @param {any} value The value to be prettified for the key associated with
+ * the prettifier.
+ * @returns {string}
+ */
+
+/**
+ * A tokenized string that indicates how the prettified log line should be
+ * formatted. Tokens are either log properties enclosed in curly braces, e.g.
+ * `{levelLabel}`, `{pid}`, or `{req.url}`, or conditional directives in curly
+ * braces. The only conditional directives supported are `if` and `end`, e.g.
+ * `{if pid}{pid}{end}`; every `if` must have a matching `end`. Nested
+ * conditions are not supported.
+ *
+ * @typedef {string} MessageFormatString
+ *
+ * @example
+ * `{levelLabel} - {if pid}{pid} - {end}url:{req.url}`
+ */
+
+/**
+ * A function that accepts a log object, name of the message key, and name of
+ * the level label key and returns a formatted log line.
+ *
+ * Note: this function must be synchronous.
+ *
+ * @typedef {function} MessageFormatFunction
+ * @param {object} log The log object to be processed.
+ * @param {string} messageKey The name of the key in the `log` object that
+ * contains the log message.
+ * @param {string} levelLabel The name of the key in the `log` object that
+ * contains the log level name.
+ * @returns {string}
+ *
+ * @example
+ * function (log, messageKey, levelLabel) {
+ *   return `${log[levelLabel]} - ${log[messageKey]}`
+ * }
+ */
diff --git a/lib/utils/interpret-conditionals.js b/lib/utils/interpret-conditionals.js
index 6a6f5d6a..cc30de8d 100644
--- a/lib/utils/interpret-conditionals.js
+++ b/lib/utils/interpret-conditionals.js
@@ -5,12 +5,13 @@ module.exports = interpretConditionals
 const getPropertyValue = require('./get-property-value')
 
 /**
- * Translates all conditional blocks from within the messageFormat. Translates any matching
- * {if key}{key}{end} statements and returns everything between if and else blocks if the key provided
- * was found in log.
+ * Translates all conditional blocks from within the messageFormat. Translates
+ * any matching {if key}{key}{end} statements and returns everything between
+ * if and else blocks if the key provided was found in log.
  *
- * @param {string} messageFormat A format string or function that defines how the
- *  logged message should be conditionally formatted, e.g. `'{if level}{level}{end} - {if req.id}{req.id}{end}'`.
+ * @param {MessageFormatString|MessageFormatFunction} messageFormat A format
+ * string or function that defines how the logged message should be
+ * conditionally formatted.
  * @param {object} log The log object to be modified.
  *
  * @returns {string} The parsed messageFormat.
diff --git a/lib/utils/join-lines-with-indentation.js b/lib/utils/join-lines-with-indentation.js
index 560bd2d1..7761ae61 100644
--- a/lib/utils/join-lines-with-indentation.js
+++ b/lib/utils/join-lines-with-indentation.js
@@ -2,16 +2,20 @@
 
 module.exports = joinLinesWithIndentation
 
+/**
+ * @typedef {object} JoinLinesWithIndentationParams
+ * @property {string} input The string to split and reformat.
+ * @property {string} [ident] The indentation string. Default: `    ` (4 spaces).
+ * @property {string} [eol] The end of line sequence to use when rejoining
+ * the lines. Default: `'\n'`.
+ */
+
 /**
  * Given a string with line separators, either `\r\n` or `\n`, add indentation
  * to all lines subsequent to the first line and rejoin the lines using an
  * end of line sequence.
  *
- * @param {object} input
- * @param {string} input.input The string to split and reformat.
- * @param {string} [input.ident] The indentation string. Default: `    ` (4 spaces).
- * @param {string} [input.eol] The end of line sequence to use when rejoining
- * the lines. Default: `'\n'`.
+ * @param {JoinLinesWithIndentationParams} input
  *
  * @returns {string} A string with lines subsequent to the first indented
  * with the given indentation sequence.
diff --git a/lib/utils/parse-factory-options.js b/lib/utils/parse-factory-options.js
new file mode 100644
index 00000000..33ffb374
--- /dev/null
+++ b/lib/utils/parse-factory-options.js
@@ -0,0 +1,153 @@
+'use strict'
+
+module.exports = parseFactoryOptions
+
+const {
+  LEVEL_NAMES
+} = require('../constants')
+const colors = require('../colors')
+const handleCustomLevelsOpts = require('./handle-custom-levels-opts')
+const handleCustomLevelsNamesOpts = require('./handle-custom-levels-names-opts')
+
+/**
+ * A `PrettyContext` is an object to be used by the various functions that
+ * process log data. It is derived from the provided {@link PinoPrettyOptions}.
+ * It may be used as a `this` context.
+ *
+ * @typedef {object} PrettyContext
+ * @property {string} EOL The escape sequence chosen as the line terminator.
+ * @property {string} IDENT The string to use as the indentation sequence.
+ * @property {ColorizerFunc} colorizer A configured colorizer function.
+ * @property {Array[Array<number, string>]} customColors A set of custom color
+ * names associated with level numbers.
+ * @property {object} customLevelNames A hash of level numbers to level names,
+ * e.g. `{ 30: "info" }`.
+ * @property {object} customLevels A hash of level names to level numbers,
+ * e.g. `{ info: 30 }`.
+ * @property {CustomPrettifiers} customPrettifiers A hash of custom prettifier
+ * functions.
+ * @property {object} customProperties Comprised of `customLevels` and
+ * `customLevelNames` if such options are provided.
+ * @property {string[]} errorLikeObjectKeys The key names in the log data that
+ * should be considered as holding error objects.
+ * @property {string[]} errorProps A list of error object keys that should be
+ * included in the output.
+ * @property {boolean} hideObject Indicates the prettifier should omit objects
+ * in the output.
+ * @property {string[]} ignoreKeys Set of log data keys to omit.
+ * @property {string[]} includeKeys Opposite of `ignoreKeys`.
+ * @property {boolean} levelFirst Indicates the level should be printed first.
+ * @property {string} levelKey Name of the key in the log data that contains
+ * the message.
+ * @property {string} levelLabel Format token to represent the position of the
+ * level name in the output string.
+ * @property {MessageFormatString|MessageFormatFunction} messageFormat
+ * @property {string} messageKey Name of the key in the log data that contains
+ * the message.
+ * @property {string|number} minimumLevel The minimum log level to process
+ * and output.
+ * @property {ColorizerFunc} objectColorizer
+ * @property {boolean} singleLine Indicates objects should be printed on a
+ * single output line.
+ * @property {string} timestampKey The name of the key in the log data that
+ * contains the log timestamp.
+ * @property {boolean} translateTime Indicates if timestamps should be
+ * translated to a human-readable string.
+ * @property {boolean} useOnlyCustomProps
+ */
+
+/**
+ * @param {PinoPrettyOptions} options The user supplied object of options.
+ *
+ * @returns {PrettyContext}
+ */
+function parseFactoryOptions (options) {
+  const EOL = options.crlf ? '\r\n' : '\n'
+  const IDENT = '    '
+  const {
+    customPrettifiers,
+    errorLikeObjectKeys,
+    hideObject,
+    levelFirst,
+    levelKey,
+    levelLabel,
+    messageFormat,
+    messageKey,
+    minimumLevel,
+    singleLine,
+    timestampKey,
+    translateTime
+  } = options
+  const errorProps = options.errorProps.split(',')
+  const useOnlyCustomProps = typeof options.useOnlyCustomProps === 'boolean'
+    ? options.useOnlyCustomProps
+    : (options.useOnlyCustomProps === 'true')
+  const customLevels = handleCustomLevelsOpts(options.customLevels)
+  const customLevelNames = handleCustomLevelsNamesOpts(options.customLevels)
+
+  let customColors
+  if (options.customColors) {
+    customColors = options.customColors.split(',').reduce((agg, value) => {
+      const [level, color] = value.split(':')
+
+      const condition = useOnlyCustomProps
+        ? options.customLevels
+        : customLevelNames[level] !== undefined
+      const levelNum = condition
+        ? customLevelNames[level]
+        : LEVEL_NAMES[level]
+      const colorIdx = levelNum !== undefined
+        ? levelNum
+        : level
+
+      agg.push([colorIdx, color])
+
+      return agg
+    }, [])
+  }
+
+  const customProperties = { customLevels, customLevelNames }
+  if (useOnlyCustomProps === true && !options.customLevels) {
+    customProperties.customLevels = undefined
+    customProperties.customLevelNames = undefined
+  }
+
+  const includeKeys = options.include !== undefined
+    ? new Set(options.include.split(','))
+    : undefined
+  const ignoreKeys = (!includeKeys && options.ignore)
+    ? new Set(options.ignore.split(','))
+    : undefined
+
+  const colorizer = colors(options.colorize, customColors, useOnlyCustomProps)
+  const objectColorizer = options.colorizeObjects
+    ? colorizer
+    : colors(false, [], false)
+
+  return {
+    EOL,
+    IDENT,
+    colorizer,
+    customColors,
+    customLevelNames,
+    customLevels,
+    customPrettifiers,
+    customProperties,
+    errorLikeObjectKeys,
+    errorProps,
+    hideObject,
+    ignoreKeys,
+    includeKeys,
+    levelFirst,
+    levelKey,
+    levelLabel,
+    messageFormat,
+    messageKey,
+    minimumLevel,
+    objectColorizer,
+    singleLine,
+    timestampKey,
+    translateTime,
+    useOnlyCustomProps
+  }
+}
diff --git a/lib/utils/prettify-error-log.js b/lib/utils/prettify-error-log.js
index 1e636b66..2410c650 100644
--- a/lib/utils/prettify-error-log.js
+++ b/lib/utils/prettify-error-log.js
@@ -3,43 +3,35 @@
 module.exports = prettifyErrorLog
 
 const {
-  ERROR_LIKE_KEYS,
-  LOGGER_KEYS,
-  MESSAGE_KEY
+  LOGGER_KEYS
 } = require('../constants')
 
 const isObject = require('./is-object')
 const joinLinesWithIndentation = require('./join-lines-with-indentation')
 const prettifyObject = require('./prettify-object')
 
+/**
+ * @typedef {object} PrettifyErrorLogParams
+ * @property {object} log The error log to prettify.
+ * @property {PrettyContext} context The context object built from parsing
+ * the options.
+ */
+
 /**
  * Given a log object that has a `type: 'Error'` key, prettify the object and
  * return the result. In other
  *
- * @param {object} input
- * @param {object} input.log The error log to prettify.
- * @param {string} [input.messageKey] The name of the key that contains a
- * general log message. This is not the error's message property but the logger
- * messsage property. Default: `MESSAGE_KEY` constant.
- * @param {string} [input.ident] The sequence to use for indentation. Default: `'    '`.
- * @param {string} [input.eol] The sequence to use for EOL. Default: `'\n'`.
- * @param {string[]} [input.errorLikeKeys] A set of keys that should be considered
- * to have error objects as values. Default: `ERROR_LIKE_KEYS` constant.
- * @param {string[]} [input.errorProperties] A set of specific error object
- * properties, that are not the value of `messageKey`, `type`, or `stack`, to
- * include in the prettified result. The first entry in the list may be `'*'`
- * to indicate that all sibling properties should be prettified. Default: `[]`.
+ * @param {PrettifyErrorLogParams} input
  *
  * @returns {string} A string that represents the prettified error log.
  */
-function prettifyErrorLog ({
-  log,
-  messageKey = MESSAGE_KEY,
-  ident = '    ',
-  eol = '\n',
-  errorLikeKeys = ERROR_LIKE_KEYS,
-  errorProperties = []
-}) {
+function prettifyErrorLog ({ log, context }) {
+  const {
+    EOL: eol,
+    IDENT: ident,
+    errorProps: errorProperties,
+    messageKey
+  } = context
   const stack = log.stack
   const joinedLines = joinLinesWithIndentation({ input: stack, ident, eol })
   let result = `${ident}${joinedLines}${eol}`
@@ -62,7 +54,14 @@ function prettifyErrorLog ({
         // The nested object may have "logger" type keys but since they are not
         // at the root level of the object being processed, we want to print them.
         // Thus, we invoke with `excludeLoggerKeys: false`.
-        const prettifiedObject = prettifyObject({ input: log[key], errorLikeKeys, excludeLoggerKeys: false, eol, ident: ident + ident })
+        const prettifiedObject = prettifyObject({
+          log: log[key],
+          excludeLoggerKeys: false,
+          context: {
+            ...context,
+            IDENT: ident + ident
+          }
+        })
         result = `${result}${ident}${key}: {${eol}${prettifiedObject}${ident}}${eol}`
         continue
       }
diff --git a/lib/utils/prettify-error-log.test.js b/lib/utils/prettify-error-log.test.js
index 5d0f7db1..04e10f82 100644
--- a/lib/utils/prettify-error-log.test.js
+++ b/lib/utils/prettify-error-log.test.js
@@ -2,22 +2,47 @@
 
 const tap = require('tap')
 const prettifyErrorLog = require('./prettify-error-log')
+const {
+  ERROR_LIKE_KEYS,
+  MESSAGE_KEY
+} = require('../constants')
+
+const context = {
+  EOL: '\n',
+  IDENT: '    ',
+  customPrettifiers: {},
+  errorLikeObjectKeys: ERROR_LIKE_KEYS,
+  errorProps: [],
+  messageKey: MESSAGE_KEY
+}
 
 tap.test('returns string with default settings', async t => {
   const err = Error('Something went wrong')
-  const str = prettifyErrorLog({ log: err })
+  const str = prettifyErrorLog({ log: err, context })
   t.ok(str.startsWith('    Error: Something went wrong'))
 })
 
 tap.test('returns string with custom ident', async t => {
   const err = Error('Something went wrong')
-  const str = prettifyErrorLog({ log: err, ident: '  ' })
+  const str = prettifyErrorLog({
+    log: err,
+    context: {
+      ...context,
+      IDENT: '  '
+    }
+  })
   t.ok(str.startsWith('  Error: Something went wrong'))
 })
 
 tap.test('returns string with custom eol', async t => {
   const err = Error('Something went wrong')
-  const str = prettifyErrorLog({ log: err, eol: '\r\n' })
+  const str = prettifyErrorLog({
+    log: err,
+    context: {
+      ...context,
+      EOL: '\r\n'
+    }
+  })
   t.ok(str.startsWith('    Error: Something went wrong\r\n'))
 })
 
@@ -25,7 +50,13 @@ tap.test('errorProperties', t => {
   t.test('excludes all for wildcard', async t => {
     const err = Error('boom')
     err.foo = 'foo'
-    const str = prettifyErrorLog({ log: err, errorProperties: ['*'] })
+    const str = prettifyErrorLog({
+      log: err,
+      context: {
+        ...context,
+        errorProps: ['*']
+      }
+    })
     t.ok(str.startsWith('    Error: boom'))
     t.equal(str.includes('foo: "foo"'), false)
   })
@@ -33,7 +64,13 @@ tap.test('errorProperties', t => {
   t.test('excludes only selected properties', async t => {
     const err = Error('boom')
     err.foo = 'foo'
-    const str = prettifyErrorLog({ log: err, errorProperties: ['foo'] })
+    const str = prettifyErrorLog({
+      log: err,
+      context: {
+        ...context,
+        errorProps: ['foo']
+      }
+    })
     t.ok(str.startsWith('    Error: boom'))
     t.equal(str.includes('foo: foo'), true)
   })
@@ -41,7 +78,13 @@ tap.test('errorProperties', t => {
   t.test('ignores specified properties if not present', async t => {
     const err = Error('boom')
     err.foo = 'foo'
-    const str = prettifyErrorLog({ log: err, errorProperties: ['foo', 'bar'] })
+    const str = prettifyErrorLog({
+      log: err,
+      context: {
+        ...context,
+        errorProps: ['foo', 'bar']
+      }
+    })
     t.ok(str.startsWith('    Error: boom'))
     t.equal(str.includes('foo: foo'), true)
     t.equal(str.includes('bar'), false)
@@ -50,7 +93,13 @@ tap.test('errorProperties', t => {
   t.test('processes nested objects', async t => {
     const err = Error('boom')
     err.foo = { bar: 'bar', message: 'included' }
-    const str = prettifyErrorLog({ log: err, errorProperties: ['foo'] })
+    const str = prettifyErrorLog({
+      log: err,
+      context: {
+        ...context,
+        errorProps: ['foo']
+      }
+    })
     t.ok(str.startsWith('    Error: boom'))
     t.equal(str.includes('foo: {'), true)
     t.equal(str.includes('bar: "bar"'), true)
diff --git a/lib/utils/prettify-error.js b/lib/utils/prettify-error.js
index 5e16cb41..ac99826a 100644
--- a/lib/utils/prettify-error.js
+++ b/lib/utils/prettify-error.js
@@ -4,14 +4,21 @@ module.exports = prettifyError
 
 const joinLinesWithIndentation = require('./join-lines-with-indentation')
 
+/**
+ * @typedef {object} PrettifyErrorParams
+ * @property {string} keyName The key assigned to this error in the log object.
+ * @property {string} lines The STRINGIFIED error. If the error field has a
+ *  custom prettifier, that should be pre-applied as well.
+ * @property {string} ident The indentation sequence to use.
+ * @property {string} eol The EOL sequence to use.
+ */
+
 /**
  * Prettifies an error string into a multi-line format.
- * @param {object} input
- * @param {string} input.keyName The key assigned to this error in the log object
- * @param {string} input.lines The STRINGIFIED error. If the error field has a
- *  custom prettifier, that should be pre-applied as well
- * @param {string} input.ident The indentation sequence to use
- * @param {string} input.eol The EOL sequence to use
+ *
+ * @param {PrettifyErrorParams} input
+ *
+ * @returns {string}
  */
 function prettifyError ({ keyName, lines, eol, ident }) {
   let result = ''
diff --git a/lib/utils/prettify-level.js b/lib/utils/prettify-level.js
index 2576965a..213ba06c 100644
--- a/lib/utils/prettify-level.js
+++ b/lib/utils/prettify-level.js
@@ -2,38 +2,33 @@
 
 module.exports = prettifyLevel
 
-const {
-  LEVEL_KEY
-} = require('../constants')
-const defaultColorizer = require('../colors')()
-
 const getPropertyValue = require('./get-property-value')
 
+/**
+ * @typedef {object} PrettifyLevelParams
+ * @property {object} log The log object.
+ * @property {PrettyContext} context The context object built from parsing
+ * the options.
+ */
+
 /**
  * Checks if the passed in log has a `level` value and returns a prettified
  * string for that level if so.
  *
- * @param {object} input
- * @param {object} input.log The log object.
- * @param {function} [input.colorizer] A colorizer function that accepts a level
- * value and returns a colorized string. Default: a no-op colorizer.
- * @param {string} [input.levelKey='level'] The key to find the level under.
- * @param {function} [input.prettifier] A user-supplied formatter to be called instead of colorizer.
- * @param {object} [input.customLevels] The custom levels where key as the level index and value as the level name.
- * @param {object} [input.customLevelNames] The custom level names where key is the level name and value is the level index.
+ * @param {PrettifyLevelParams} input
  *
  * @returns {undefined|string} If `log` does not have a `level` property then
  * `undefined` will be returned. Otherwise, a string from the specified
  * `colorizer` is returned.
  */
-function prettifyLevel ({
-  log,
-  colorizer = defaultColorizer,
-  levelKey = LEVEL_KEY,
-  prettifier,
-  customLevels,
-  customLevelNames
-}) {
+function prettifyLevel ({ log, context }) {
+  const {
+    colorizer,
+    customLevels,
+    customLevelNames,
+    levelKey
+  } = context
+  const prettifier = context.customPrettifiers?.level
   const output = getPropertyValue(log, levelKey)
   if (output === undefined) return undefined
   return prettifier ? prettifier(output) : colorizer(output, { customLevels, customLevelNames })
diff --git a/lib/utils/prettify-level.test.js b/lib/utils/prettify-level.test.js
index e127da10..e735b0a9 100644
--- a/lib/utils/prettify-level.test.js
+++ b/lib/utils/prettify-level.test.js
@@ -3,9 +3,25 @@
 const tap = require('tap')
 const prettifyLevel = require('./prettify-level')
 const getColorizer = require('../colors')
+const {
+  LEVEL_KEY
+} = require('../constants')
+
+const context = {
+  colorizer: getColorizer(),
+  customLevelNames: undefined,
+  customLevels: undefined,
+  levelKey: LEVEL_KEY,
+  customPrettifiers: undefined
+}
 
 tap.test('returns `undefined` for unknown level', async t => {
-  const colorized = prettifyLevel({ log: {} })
+  const colorized = prettifyLevel({
+    log: {},
+    context: {
+      ...context
+    }
+  })
   t.equal(colorized, undefined)
 })
 
@@ -13,7 +29,12 @@ tap.test('returns non-colorized value for default colorizer', async t => {
   const log = {
     level: 30
   }
-  const colorized = prettifyLevel({ log })
+  const colorized = prettifyLevel({
+    log,
+    context: {
+      ...context
+    }
+  })
   t.equal(colorized, 'INFO')
 })
 
@@ -22,7 +43,13 @@ tap.test('returns colorized value for color colorizer', async t => {
     level: 30
   }
   const colorizer = getColorizer(true)
-  const colorized = prettifyLevel({ log, colorizer })
+  const colorized = prettifyLevel({
+    log,
+    context: {
+      ...context,
+      colorizer
+    }
+  })
   t.equal(colorized, '\u001B[32mINFO\u001B[39m')
 })
 
@@ -30,10 +57,12 @@ tap.test('passes output through provided prettifier', async t => {
   const log = {
     level: 30
   }
-  const colorized = prettifyLevel({ log, prettifier })
+  const colorized = prettifyLevel({
+    log,
+    context: {
+      ...context,
+      customPrettifiers: { level () { return 'modified' } }
+    }
+  })
   t.equal(colorized, 'modified')
-
-  function prettifier () {
-    return 'modified'
-  }
 })
diff --git a/lib/utils/prettify-message.js b/lib/utils/prettify-message.js
index b6c8a649..7758f1d8 100644
--- a/lib/utils/prettify-message.js
+++ b/lib/utils/prettify-message.js
@@ -2,46 +2,39 @@
 
 module.exports = prettifyMessage
 
-const defaultColorizer = require('../colors')()
 const {
-  LEVELS,
-  LEVEL_KEY,
-  LEVEL_LABEL,
-  MESSAGE_KEY
+  LEVELS
 } = require('../constants')
 
 const getPropertyValue = require('./get-property-value')
 const interpretConditionals = require('./interpret-conditionals')
 
+/**
+ * @typedef {object} PrettifyMessageParams
+ * @property {object} log The log object with the message to colorize.
+ * @property {PrettyContext} context The context object built from parsing
+ * the options.
+ */
+
 /**
  * Prettifies a message string if the given `log` has a message property.
  *
- * @param {object} input
- * @param {object} input.log The log object with the message to colorize.
- * @param {string} [input.messageKey='msg'] The property of the `log` that is the
- * message to be prettified.
- * @param {string|function} [input.messageFormat=undefined] A format string or function that defines how the
- *  logged message should be formatted, e.g. `'{level} - {pid}'`.
- * @param {function} [input.colorizer] A colorizer function that has a
- * `.message(str)` method attached to it. This function should return a colorized
- * string which will be the "prettified" message. Default: a no-op colorizer.
- * @param {string} [input.levelLabel='levelLabel'] The label used to output the log level
- * @param {string} [input.levelKey='level'] The key to find the level under.
- * @param {object} [input.customLevels] The custom levels where key as the level index and value as the level name.
+ * @param {PrettifyMessageParams} input
  *
  * @returns {undefined|string} If the message key is not found, or the message
  * key is not a string, then `undefined` will be returned. Otherwise, a string
  * that is the prettified message.
  */
-function prettifyMessage ({
-  log,
-  messageFormat,
-  messageKey = MESSAGE_KEY,
-  colorizer = defaultColorizer,
-  levelLabel = LEVEL_LABEL,
-  levelKey = LEVEL_KEY,
-  customLevels, useOnlyCustomProps
-}) {
+function prettifyMessage ({ log, context }) {
+  const {
+    colorizer,
+    customLevels,
+    levelKey,
+    levelLabel,
+    messageFormat,
+    messageKey,
+    useOnlyCustomProps
+  } = context
   if (messageFormat && typeof messageFormat === 'string') {
     const parsedMessageFormat = interpretConditionals(messageFormat, log)
 
diff --git a/lib/utils/prettify-message.test.js b/lib/utils/prettify-message.test.js
index 061f7131..8faf4b12 100644
--- a/lib/utils/prettify-message.test.js
+++ b/lib/utils/prettify-message.test.js
@@ -3,96 +3,184 @@
 const tap = require('tap')
 const prettifyMessage = require('./prettify-message')
 const getColorizer = require('../colors')
+const {
+  LEVEL_KEY,
+  LEVEL_LABEL
+} = require('../constants')
+const context = {
+  colorizer: getColorizer(),
+  levelKey: LEVEL_KEY,
+  levelLabel: LEVEL_LABEL,
+  messageKey: 'msg'
+}
 
 tap.test('returns `undefined` if `messageKey` not found', async t => {
-  const str = prettifyMessage({ log: {} })
+  const str = prettifyMessage({ log: {}, context })
   t.equal(str, undefined)
 })
 
 tap.test('returns `undefined` if `messageKey` not string', async t => {
-  const str = prettifyMessage({ log: { msg: {} } })
+  const str = prettifyMessage({ log: { msg: {} }, context })
   t.equal(str, undefined)
 })
 
 tap.test('returns non-colorized value for default colorizer', async t => {
-  const str = prettifyMessage({ log: { msg: 'foo' } })
+  const colorizer = getColorizer()
+  const str = prettifyMessage({
+    log: { msg: 'foo' },
+    context: { ...context, colorizer }
+  })
   t.equal(str, 'foo')
 })
 
 tap.test('returns non-colorized value for alternate `messageKey`', async t => {
-  const str = prettifyMessage({ log: { message: 'foo' }, messageKey: 'message' })
+  const str = prettifyMessage({
+    log: { message: 'foo' },
+    context: { ...context, messageKey: 'message' }
+  })
   t.equal(str, 'foo')
 })
 
 tap.test('returns colorized value for color colorizer', async t => {
   const colorizer = getColorizer(true)
-  const str = prettifyMessage({ log: { msg: 'foo' }, colorizer })
+  const str = prettifyMessage({
+    log: { msg: 'foo' },
+    context: { ...context, colorizer }
+  })
   t.equal(str, '\u001B[36mfoo\u001B[39m')
 })
 
 tap.test('returns colorized value for color colorizer for alternate `messageKey`', async t => {
   const colorizer = getColorizer(true)
-  const str = prettifyMessage({ log: { message: 'foo' }, messageKey: 'message', colorizer })
+  const str = prettifyMessage({
+    log: { message: 'foo' },
+    context: { ...context, messageKey: 'message', colorizer }
+  })
   t.equal(str, '\u001B[36mfoo\u001B[39m')
 })
 
 tap.test('returns message formatted by `messageFormat` option', async t => {
-  const str = prettifyMessage({ log: { msg: 'foo', context: 'appModule' }, messageFormat: '{context} - {msg}' })
+  const str = prettifyMessage({
+    log: { msg: 'foo', context: 'appModule' },
+    context: { ...context, messageFormat: '{context} - {msg}' }
+  })
   t.equal(str, 'appModule - foo')
 })
 
 tap.test('returns message formatted by `messageFormat` option - missing prop', async t => {
-  const str = prettifyMessage({ log: { context: 'appModule' }, messageFormat: '{context} - {msg}' })
+  const str = prettifyMessage({
+    log: { context: 'appModule' },
+    context: { ...context, messageFormat: '{context} - {msg}' }
+  })
   t.equal(str, 'appModule - ')
 })
 
 tap.test('returns message formatted by `messageFormat` option - levelLabel & useOnlyCustomProps false', async t => {
-  const str = prettifyMessage({ log: { msg: 'foo', context: 'appModule', level: 30 }, messageFormat: '[{level}] {levelLabel} {context} - {msg}', customLevels: {} })
+  const str = prettifyMessage({
+    log: { msg: 'foo', context: 'appModule', level: 30 },
+    context: {
+      ...context,
+      messageFormat: '[{level}] {levelLabel} {context} - {msg}',
+      customLevels: {}
+    }
+  })
   t.equal(str, '[30] INFO appModule - foo')
 })
 
 tap.test('returns message formatted by `messageFormat` option - levelLabel & useOnlyCustomProps true', async t => {
-  const str = prettifyMessage({ log: { msg: 'foo', context: 'appModule', level: 30 }, messageFormat: '[{level}] {levelLabel} {context} - {msg}', customLevels: { 30: 'CHECK' }, useOnlyCustomProps: true })
+  const str = prettifyMessage({
+    log: { msg: 'foo', context: 'appModule', level: 30 },
+    context: {
+      ...context,
+      messageFormat: '[{level}] {levelLabel} {context} - {msg}',
+      customLevels: { 30: 'CHECK' },
+      useOnlyCustomProps: true
+    }
+  })
   t.equal(str, '[30] CHECK appModule - foo')
 })
 
 tap.test('returns message formatted by `messageFormat` option - levelLabel & customLevels', async t => {
-  const str = prettifyMessage({ log: { msg: 'foo', context: 'appModule', level: 123 }, messageFormat: '[{level}] {levelLabel} {context} - {msg}', customLevels: { 123: 'CUSTOM' } })
+  const str = prettifyMessage({
+    log: { msg: 'foo', context: 'appModule', level: 123 },
+    context: {
+      ...context,
+      messageFormat: '[{level}] {levelLabel} {context} - {msg}',
+      customLevels: { 123: 'CUSTOM' }
+    }
+  })
   t.equal(str, '[123] CUSTOM appModule - foo')
 })
 
 tap.test('returns message formatted by `messageFormat` option - levelLabel, customLevels & useOnlyCustomProps', async t => {
-  const str = prettifyMessage({ log: { msg: 'foo', context: 'appModule', level: 123 }, messageFormat: '[{level}] {levelLabel} {context} - {msg}', customLevels: { 123: 'CUSTOM' }, useOnlyCustomProps: true })
+  const str = prettifyMessage({
+    log: { msg: 'foo', context: 'appModule', level: 123 },
+    context: {
+      ...context,
+      messageFormat: '[{level}] {levelLabel} {context} - {msg}',
+      customLevels: { 123: 'CUSTOM' },
+      useOnlyCustomProps: true
+    }
+  })
   t.equal(str, '[123] CUSTOM appModule - foo')
 })
 
 tap.test('returns message formatted by `messageFormat` option - levelLabel, customLevels & useOnlyCustomProps false', async t => {
-  const str = prettifyMessage({ log: { msg: 'foo', context: 'appModule', level: 40 }, messageFormat: '[{level}] {levelLabel} {context} - {msg}', customLevels: { 123: 'CUSTOM' }, useOnlyCustomProps: false })
+  const str = prettifyMessage({
+    log: { msg: 'foo', context: 'appModule', level: 40 },
+    context: {
+      ...context,
+      messageFormat: '[{level}] {levelLabel} {context} - {msg}',
+      customLevels: { 123: 'CUSTOM' },
+      useOnlyCustomProps: false
+    }
+  })
   t.equal(str, '[40] WARN appModule - foo')
 })
 
 tap.test('`messageFormat` supports nested curly brackets', async t => {
-  const str = prettifyMessage({ log: { level: 30 }, messageFormat: '{{level}}-{level}-{{level}-{level}}' })
+  const str = prettifyMessage({
+    log: { level: 30 },
+    context: {
+      ...context,
+      messageFormat: '{{level}}-{level}-{{level}-{level}}'
+    }
+  })
   t.equal(str, '{30}-30-{30-30}')
 })
 
 tap.test('`messageFormat` supports nested object', async t => {
-  const str = prettifyMessage({ log: { level: 30, request: { url: 'localhost/test' }, msg: 'foo' }, messageFormat: '{request.url} - param: {request.params.process} - {msg}' })
+  const str = prettifyMessage({
+    log: { level: 30, request: { url: 'localhost/test' }, msg: 'foo' },
+    context: {
+      ...context,
+      messageFormat: '{request.url} - param: {request.params.process} - {msg}'
+    }
+  })
   t.equal(str, 'localhost/test - param:  - foo')
 })
 
 tap.test('`messageFormat` supports conditional blocks', async t => {
-  const str = prettifyMessage({ log: { level: 30, req: { id: 'foo' } }, messageFormat: '{level} | {if req.id}({req.id}){end}{if msg}{msg}{end}' })
+  const str = prettifyMessage({
+    log: { level: 30, req: { id: 'foo' } },
+    context: {
+      ...context,
+      messageFormat: '{level} | {if req.id}({req.id}){end}{if msg}{msg}{end}'
+    }
+  })
   t.equal(str, '30 | (foo)')
 })
 
 tap.test('`messageFormat` supports function definition', async t => {
   const str = prettifyMessage({
     log: { level: 30, request: { url: 'localhost/test' }, msg: 'incoming request' },
-    messageFormat: (log, messageKey, levelLabel) => {
-      let msg = log[messageKey]
-      if (msg === 'incoming request') msg = `--> ${log.request.url}`
-      return msg
+    context: {
+      ...context,
+      messageFormat: (log, messageKey, levelLabel) => {
+        let msg = log[messageKey]
+        if (msg === 'incoming request') msg = `--> ${log.request.url}`
+        return msg
+      }
     }
   })
   t.equal(str, '--> localhost/test')
diff --git a/lib/utils/prettify-metadata.js b/lib/utils/prettify-metadata.js
index cc55b71f..5fb4a3b0 100644
--- a/lib/utils/prettify-metadata.js
+++ b/lib/utils/prettify-metadata.js
@@ -2,23 +2,26 @@
 
 module.exports = prettifyMetadata
 
+/**
+ * @typedef {object} PrettifyMetadataParams
+ * @property {object} log The log that may or may not contain metadata to
+ * be prettified.
+ * @property {PrettyContext} context The context object built from parsing
+ * the options.
+ */
+
 /**
  * Prettifies metadata that is usually present in a Pino log line. It looks for
  * fields `name`, `pid`, `hostname`, and `caller` and returns a formatted string using
  * the fields it finds.
  *
- * @param {object} input
- * @param {object} input.log The log that may or may not contain metadata to
- * be prettified.
- * @param {object} input.prettifiers A set of functions used to prettify each
- * key of the input log's metadata. The keys are the keys of the metadata (like
- * `hostname`, `pid`, `name`, etc), and the values are functions which take the
- * metadata value and return a string. Each key is optional.
+ * @param {PrettifyMetadataParams} input
  *
  * @returns {undefined|string} If no metadata is found then `undefined` is
  * returned. Otherwise, a string of prettified metadata is returned.
  */
-function prettifyMetadata ({ log, prettifiers = {} }) {
+function prettifyMetadata ({ log, context }) {
+  const prettifiers = context.customPrettifiers
   let line = ''
 
   if (log.name || log.pid || log.hostname) {
diff --git a/lib/utils/prettify-metadata.test.js b/lib/utils/prettify-metadata.test.js
index e665db0e..91281a96 100644
--- a/lib/utils/prettify-metadata.test.js
+++ b/lib/utils/prettify-metadata.test.js
@@ -2,84 +2,87 @@
 
 const tap = require('tap')
 const prettifyMetadata = require('./prettify-metadata')
+const context = {
+  customPrettifiers: {}
+}
 
 tap.test('returns `undefined` if no metadata present', async t => {
-  const str = prettifyMetadata({ log: {} })
+  const str = prettifyMetadata({ log: {}, context })
   t.equal(str, undefined)
 })
 
 tap.test('works with only `name` present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo' } })
+  const str = prettifyMetadata({ log: { name: 'foo' }, context })
   t.equal(str, '(foo)')
 })
 
 tap.test('works with only `pid` present', async t => {
-  const str = prettifyMetadata({ log: { pid: '1234' } })
+  const str = prettifyMetadata({ log: { pid: '1234' }, context })
   t.equal(str, '(1234)')
 })
 
 tap.test('works with only `hostname` present', async t => {
-  const str = prettifyMetadata({ log: { hostname: 'bar' } })
+  const str = prettifyMetadata({ log: { hostname: 'bar' }, context })
   t.equal(str, '(on bar)')
 })
 
 tap.test('works with only `name` & `pid` present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234' } })
+  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234' }, context })
   t.equal(str, '(foo/1234)')
 })
 
 tap.test('works with only `name` & `hostname` present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo', hostname: 'bar' } })
+  const str = prettifyMetadata({ log: { name: 'foo', hostname: 'bar' }, context })
   t.equal(str, '(foo on bar)')
 })
 
 tap.test('works with only `pid` & `hostname` present', async t => {
-  const str = prettifyMetadata({ log: { pid: '1234', hostname: 'bar' } })
+  const str = prettifyMetadata({ log: { pid: '1234', hostname: 'bar' }, context })
   t.equal(str, '(1234 on bar)')
 })
 
 tap.test('works with only `name`, `pid`, & `hostname` present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234', hostname: 'bar' } })
+  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234', hostname: 'bar' }, context })
   t.equal(str, '(foo/1234 on bar)')
 })
 
 tap.test('works with only `name` & `caller` present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo', caller: 'baz' } })
+  const str = prettifyMetadata({ log: { name: 'foo', caller: 'baz' }, context })
   t.equal(str, '(foo) <baz>')
 })
 
 tap.test('works with only `pid` & `caller` present', async t => {
-  const str = prettifyMetadata({ log: { pid: '1234', caller: 'baz' } })
+  const str = prettifyMetadata({ log: { pid: '1234', caller: 'baz' }, context })
   t.equal(str, '(1234) <baz>')
 })
 
 tap.test('works with only `hostname` & `caller` present', async t => {
-  const str = prettifyMetadata({ log: { hostname: 'bar', caller: 'baz' } })
+  const str = prettifyMetadata({ log: { hostname: 'bar', caller: 'baz' }, context })
   t.equal(str, '(on bar) <baz>')
 })
 
 tap.test('works with only `name`, `pid`, & `caller` present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234', caller: 'baz' } })
+  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234', caller: 'baz' }, context })
   t.equal(str, '(foo/1234) <baz>')
 })
 
 tap.test('works with only `name`, `hostname`, & `caller` present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo', hostname: 'bar', caller: 'baz' } })
+  const str = prettifyMetadata({ log: { name: 'foo', hostname: 'bar', caller: 'baz' }, context })
   t.equal(str, '(foo on bar) <baz>')
 })
 
 tap.test('works with only `caller` present', async t => {
-  const str = prettifyMetadata({ log: { caller: 'baz' } })
+  const str = prettifyMetadata({ log: { caller: 'baz' }, context })
   t.equal(str, '<baz>')
 })
 
 tap.test('works with only `pid`, `hostname`, & `caller` present', async t => {
-  const str = prettifyMetadata({ log: { pid: '1234', hostname: 'bar', caller: 'baz' } })
+  const str = prettifyMetadata({ log: { pid: '1234', hostname: 'bar', caller: 'baz' }, context })
   t.equal(str, '(1234 on bar) <baz>')
 })
 
 tap.test('works with all four present', async t => {
-  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234', hostname: 'bar', caller: 'baz' } })
+  const str = prettifyMetadata({ log: { name: 'foo', pid: '1234', hostname: 'bar', caller: 'baz' }, context })
   t.equal(str, '(foo/1234 on bar) <baz>')
 })
 
@@ -100,7 +103,9 @@ tap.test('uses prettifiers from passed prettifiers object', async t => {
   }
   const str = prettifyMetadata({
     log: { pid: '1234', hostname: 'bar', caller: 'baz', name: 'joe' },
-    prettifiers
+    context: {
+      customPrettifiers: prettifiers
+    }
   })
   t.equal(str, '(JOE/1234__ on BAR) <BAZ>')
 })
diff --git a/lib/utils/prettify-object.js b/lib/utils/prettify-object.js
index acee463f..e4bbec1c 100644
--- a/lib/utils/prettify-object.js
+++ b/lib/utils/prettify-object.js
@@ -3,50 +3,48 @@
 module.exports = prettifyObject
 
 const {
-  ERROR_LIKE_KEYS,
   LOGGER_KEYS
 } = require('../constants')
-const defaultColorizer = require('../colors')()
 
 const stringifySafe = require('fast-safe-stringify')
 const joinLinesWithIndentation = require('./join-lines-with-indentation')
 const prettifyError = require('./prettify-error')
 
+/**
+ * @typedef {object} PrettifyObjectParams
+ * @property {object} log The object to prettify.
+ * @property {boolean} [excludeLoggerKeys] Indicates if known logger specific
+ * keys should be excluded from prettification. Default: `true`.
+ * @property {string[]} [skipKeys] A set of object keys to exclude from the
+ *  * prettified result. Default: `[]`.
+ * @property {PrettyContext} context The context object built from parsing
+ * the options.
+ */
+
 /**
  * Prettifies a standard object. Special care is taken when processing the object
  * to handle child objects that are attached to keys known to contain error
  * objects.
  *
- * @param {object} input
- * @param {object} input.input The object to prettify.
- * @param {string} [input.ident] The identation sequence to use. Default: `'    '`.
- * @param {string} [input.eol] The EOL sequence to use. Default: `'\n'`.
- * @param {string[]} [input.skipKeys] A set of object keys to exclude from the
- * prettified result. Default: `[]`.
- * @param {Object<string, function>} [input.customPrettifiers] Dictionary of
- * custom prettifiers. Default: `{}`.
- * @param {string[]} [input.errorLikeKeys] A set of object keys that contain
- * error objects. Default: `ERROR_LIKE_KEYS` constant.
- * @param {boolean} [input.excludeLoggerKeys] Indicates if known logger specific
- * keys should be excluded from prettification. Default: `true`.
- * @param {boolean} [input.singleLine] Should non-error keys all be formatted
- * on a single line? This does NOT apply to errors, which will still be
- * multi-line. Default: `false`
+ * @param {PrettifyObjectParams} input
  *
  * @returns {string} The prettified string. This can be as little as `''` if
  * there was nothing to prettify.
  */
 function prettifyObject ({
-  input,
-  ident = '    ',
-  eol = '\n',
-  skipKeys = [],
-  customPrettifiers = {},
-  errorLikeKeys = ERROR_LIKE_KEYS,
+  log,
   excludeLoggerKeys = true,
-  singleLine = false,
-  colorizer = defaultColorizer
+  skipKeys = [],
+  context
 }) {
+  const {
+    EOL: eol,
+    IDENT: ident,
+    customPrettifiers,
+    errorLikeObjectKeys: errorLikeKeys,
+    objectColorizer,
+    singleLine
+  } = context
   const keysToIgnore = [].concat(skipKeys)
 
   /* istanbul ignore else */
@@ -55,11 +53,11 @@ function prettifyObject ({
   let result = ''
 
   // Split object keys into two categories: error and non-error
-  const { plain, errors } = Object.entries(input).reduce(({ plain, errors }, [k, v]) => {
+  const { plain, errors } = Object.entries(log).reduce(({ plain, errors }, [k, v]) => {
     if (keysToIgnore.includes(k) === false) {
       // Pre-apply custom prettifiers, because all 3 cases below will need this
       const pretty = typeof customPrettifiers[k] === 'function'
-        ? customPrettifiers[k](v, k, input)
+        ? customPrettifiers[k](v, k, log)
         : v
       if (errorLikeKeys.includes(k)) {
         errors[k] = pretty
@@ -74,7 +72,7 @@ function prettifyObject ({
     // Stringify the entire object as a single JSON line
     /* istanbul ignore else */
     if (Object.keys(plain).length > 0) {
-      result += colorizer.greyMessage(stringifySafe(plain))
+      result += objectColorizer.greyMessage(stringifySafe(plain))
     }
     result += eol
     // Avoid printing the escape character on escaped backslashes.
diff --git a/lib/utils/prettify-object.test.js b/lib/utils/prettify-object.test.js
index a3492403..61f64244 100644
--- a/lib/utils/prettify-object.test.js
+++ b/lib/utils/prettify-object.test.js
@@ -1,40 +1,63 @@
 'use strict'
 
 const tap = require('tap')
+const colors = require('../colors')
 const prettifyObject = require('./prettify-object')
+const {
+  ERROR_LIKE_KEYS
+} = require('../constants')
+
+const context = {
+  EOL: '\n',
+  IDENT: '    ',
+  customPrettifiers: {},
+  errorLikeObjectKeys: ERROR_LIKE_KEYS,
+  objectColorizer: colors(),
+  singleLine: false
+}
 
 tap.test('returns empty string if no properties present', async t => {
-  const str = prettifyObject({ input: {} })
+  const str = prettifyObject({ log: {}, context })
   t.equal(str, '')
 })
 
 tap.test('works with single level properties', async t => {
-  const str = prettifyObject({ input: { foo: 'bar' } })
+  const str = prettifyObject({ log: { foo: 'bar' }, context })
   t.equal(str, '    foo: "bar"\n')
 })
 
 tap.test('works with multiple level properties', async t => {
-  const str = prettifyObject({ input: { foo: { bar: 'baz' } } })
+  const str = prettifyObject({ log: { foo: { bar: 'baz' } }, context })
   t.equal(str, '    foo: {\n      "bar": "baz"\n    }\n')
 })
 
 tap.test('skips specified keys', async t => {
-  const str = prettifyObject({ input: { foo: 'bar', hello: 'world' }, skipKeys: ['foo'] })
+  const str = prettifyObject({
+    log: { foo: 'bar', hello: 'world' },
+    skipKeys: ['foo'],
+    context
+  })
   t.equal(str, '    hello: "world"\n')
 })
 
 tap.test('ignores predefined keys', async t => {
-  const str = prettifyObject({ input: { foo: 'bar', pid: 12345 } })
+  const str = prettifyObject({ log: { foo: 'bar', pid: 12345 }, context })
   t.equal(str, '    foo: "bar"\n')
 })
 
 tap.test('ignores escaped backslashes in string values', async t => {
-  const str = prettifyObject({ input: { foo_regexp: '\\[^\\w\\s]\\' } })
+  const str = prettifyObject({ log: { foo_regexp: '\\[^\\w\\s]\\' }, context })
   t.equal(str, '    foo_regexp: "\\[^\\w\\s]\\"\n')
 })
 
 tap.test('ignores escaped backslashes in string values (singleLine option)', async t => {
-  const str = prettifyObject({ input: { foo_regexp: '\\[^\\w\\s]\\' }, singleLine: true })
+  const str = prettifyObject({
+    log: { foo_regexp: '\\[^\\w\\s]\\' },
+    context: {
+      ...context,
+      singleLine: true
+    }
+  })
   t.equal(str, '{"foo_regexp":"\\[^\\w\\s]\\"}\n')
 })
 
@@ -44,7 +67,7 @@ tap.test('works with error props', async t => {
     message: err.message,
     stack: err.stack
   }
-  const str = prettifyObject({ input: { error: serializedError } })
+  const str = prettifyObject({ log: { error: serializedError }, context })
   t.ok(str.startsWith('    error:'))
   t.ok(str.includes('     "message": "Something went wrong",'))
   t.ok(str.includes('         Error: Something went wrong'))
@@ -54,7 +77,13 @@ tap.test('customPrettifiers gets applied', async t => {
   const customPrettifiers = {
     foo: v => v.toUpperCase()
   }
-  const str = prettifyObject({ input: { foo: 'foo' }, customPrettifiers })
+  const str = prettifyObject({
+    log: { foo: 'foo' },
+    context: {
+      ...context,
+      customPrettifiers
+    }
+  })
   t.equal(str.startsWith('    foo: FOO'), true)
 })
 
@@ -62,17 +91,26 @@ tap.test('skips lines omitted by customPrettifiers', async t => {
   const customPrettifiers = {
     foo: () => { return undefined }
   }
-  const str = prettifyObject({ input: { foo: 'foo', bar: 'bar' }, customPrettifiers })
+  const str = prettifyObject({
+    log: { foo: 'foo', bar: 'bar' },
+    context: {
+      ...context,
+      customPrettifiers
+    }
+  })
   t.equal(str.includes('bar: "bar"'), true)
   t.equal(str.includes('foo: "foo"'), false)
 })
 
 tap.test('joined lines omits starting eol', async t => {
   const str = prettifyObject({
-    input: { msg: 'doing work', calls: ['step 1', 'step 2', 'step 3'], level: 30 },
-    ident: '',
-    customPrettifiers: {
-      calls: val => '\n' + val.map(it => '  ' + it).join('\n')
+    log: { msg: 'doing work', calls: ['step 1', 'step 2', 'step 3'], level: 30 },
+    context: {
+      ...context,
+      IDENT: '',
+      customPrettifiers: {
+        calls: val => '\n' + val.map(it => '  ' + it).join('\n')
+      }
     }
   })
   t.equal(str, [
@@ -89,7 +127,13 @@ tap.test('errors skips prettifiers', async t => {
   const customPrettifiers = {
     err: () => { return 'is_err' }
   }
-  const str = prettifyObject({ input: { err: Error('boom') }, customPrettifiers })
+  const str = prettifyObject({
+    log: { err: Error('boom') },
+    context: {
+      ...context,
+      customPrettifiers
+    }
+  })
   t.equal(str.includes('err: is_err'), true)
 })
 
@@ -97,6 +141,12 @@ tap.test('errors skips prettifying if no lines are present', async t => {
   const customPrettifiers = {
     err: () => { return undefined }
   }
-  const str = prettifyObject({ input: { err: Error('boom') }, customPrettifiers })
+  const str = prettifyObject({
+    log: { err: Error('boom') },
+    context: {
+      ...context,
+      customPrettifiers
+    }
+  })
   t.equal(str, '')
 })
diff --git a/lib/utils/prettify-time.js b/lib/utils/prettify-time.js
index 9a3ce2dd..e876b35f 100644
--- a/lib/utils/prettify-time.js
+++ b/lib/utils/prettify-time.js
@@ -2,35 +2,31 @@
 
 module.exports = prettifyTime
 
-const {
-  TIMESTAMP_KEY
-} = require('../constants')
-
 const formatTime = require('./format-time')
 
+/**
+ * @typedef {object} PrettifyTimeParams
+ * @property {object} log The log object with the timestamp to be prettified.
+ * @property {PrettyContext} context The context object built from parsing
+ * the options.
+ */
+
 /**
  * Prettifies a timestamp if the given `log` has either `time`, `timestamp` or custom specified timestamp
  * property.
  *
- * @param {object} input
- * @param {object} input.log The log object with the timestamp to be prettified.
- * @param {string} [input.timestampKey='time'] The log property that should be used to resolve timestamp value
- * @param {boolean|string} [input.translateFormat=undefined] When `true` the
- * timestamp will be prettified into a string at UTC using the default
- * `DATE_FORMAT`. If a string, then `translateFormat` will be used as the format
- * string to determine the output; see the `formatTime` function for details.
- * @param {function} [input.prettifier] A user-supplied formatter for altering output.
+ * @param {PrettifyTimeParams} input
  *
  * @returns {undefined|string} If a timestamp property cannot be found then
  * `undefined` is returned. Otherwise, the prettified time is returned as a
  * string.
  */
-function prettifyTime ({
-  log,
-  timestampKey = TIMESTAMP_KEY,
-  translateFormat = undefined,
-  prettifier
-}) {
+function prettifyTime ({ log, context }) {
+  const {
+    timestampKey,
+    translateTime: translateFormat
+  } = context
+  const prettifier = context.customPrettifiers?.time
   let time = null
 
   if (timestampKey in log) {
diff --git a/lib/utils/prettify-time.test.js b/lib/utils/prettify-time.test.js
index 1b3e55e5..cfde6627 100644
--- a/lib/utils/prettify-time.test.js
+++ b/lib/utils/prettify-time.test.js
@@ -4,80 +4,181 @@ process.env.TZ = 'UTC'
 
 const tap = require('tap')
 const prettifyTime = require('./prettify-time')
+const {
+  TIMESTAMP_KEY
+} = require('../constants')
+const context = {
+  timestampKey: TIMESTAMP_KEY,
+  translateTime: true,
+  customPrettifiers: {}
+}
 
 tap.test('returns `undefined` if `time` or `timestamp` not in log', async t => {
-  const str = prettifyTime({ log: {} })
+  const str = prettifyTime({ log: {}, context })
   t.equal(str, undefined)
 })
 
 tap.test('returns prettified formatted time from custom field', async t => {
   const log = { customtime: 1554642900000 }
-  let str = prettifyTime({ log, translateFormat: true, timestampKey: 'customtime' })
+  let str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      timestampKey: 'customtime'
+    }
+  })
   t.equal(str, '[13:15:00.000]')
 
-  str = prettifyTime({ log, translateFormat: false, timestampKey: 'customtime' })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: false,
+      timestampKey: 'customtime'
+    }
+  })
   t.equal(str, '[1554642900000]')
 })
 
 tap.test('returns prettified formatted time', async t => {
   let log = { time: 1554642900000 }
-  let str = prettifyTime({ log, translateFormat: true })
+  let str = prettifyTime({
+    log,
+    context: {
+      ...context
+    }
+  })
   t.equal(str, '[13:15:00.000]')
 
   log = { timestamp: 1554642900000 }
-  str = prettifyTime({ log, translateFormat: true })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context
+    }
+  })
   t.equal(str, '[13:15:00.000]')
 
   log = { time: '2019-04-07T09:15:00.000-04:00' }
-  str = prettifyTime({ log, translateFormat: true })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context
+    }
+  })
   t.equal(str, '[13:15:00.000]')
 
   log = { timestamp: '2019-04-07T09:15:00.000-04:00' }
-  str = prettifyTime({ log, translateFormat: true })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context
+    }
+  })
   t.equal(str, '[13:15:00.000]')
 
   log = { time: 1554642900000 }
-  str = prettifyTime({ log, translateFormat: 'd mmm yyyy H:MM' })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: 'd mmm yyyy H:MM'
+    }
+  })
   t.equal(str, '[7 Apr 2019 13:15]')
 
   log = { timestamp: 1554642900000 }
-  str = prettifyTime({ log, translateFormat: 'd mmm yyyy H:MM' })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: 'd mmm yyyy H:MM'
+    }
+  })
   t.equal(str, '[7 Apr 2019 13:15]')
 
   log = { time: '2019-04-07T09:15:00.000-04:00' }
-  str = prettifyTime({ log, translateFormat: 'd mmm yyyy H:MM' })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: 'd mmm yyyy H:MM'
+    }
+  })
   t.equal(str, '[7 Apr 2019 13:15]')
 
   log = { timestamp: '2019-04-07T09:15:00.000-04:00' }
-  str = prettifyTime({ log, translateFormat: 'd mmm yyyy H:MM' })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: 'd mmm yyyy H:MM'
+    }
+  })
   t.equal(str, '[7 Apr 2019 13:15]')
 })
 
 tap.test('passes through value', async t => {
   let log = { time: 1554642900000 }
-  let str = prettifyTime({ log })
+  let str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: undefined
+    }
+  })
   t.equal(str, '[1554642900000]')
 
   log = { timestamp: 1554642900000 }
-  str = prettifyTime({ log })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: undefined
+    }
+  })
   t.equal(str, '[1554642900000]')
 
   log = { time: '2019-04-07T09:15:00.000-04:00' }
-  str = prettifyTime({ log })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: undefined
+    }
+  })
   t.equal(str, '[2019-04-07T09:15:00.000-04:00]')
 
   log = { timestamp: '2019-04-07T09:15:00.000-04:00' }
-  str = prettifyTime({ log })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: undefined
+    }
+  })
   t.equal(str, '[2019-04-07T09:15:00.000-04:00]')
 })
 
 tap.test('handles the 0 timestamp', async t => {
   let log = { time: 0 }
-  let str = prettifyTime({ log })
+  let str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: undefined
+    }
+  })
   t.equal(str, '[0]')
 
   log = { timestamp: 0 }
-  str = prettifyTime({ log })
+  str = prettifyTime({
+    log,
+    context: {
+      ...context,
+      translateTime: undefined
+    }
+  })
   t.equal(str, '[0]')
 })
 
@@ -86,15 +187,24 @@ tap.test('works with epoch as a number or string', (t) => {
   const epoch = 1522431328992
   const asNumber = prettifyTime({
     log: { time: epoch, msg: 'foo' },
-    translateFormat: true
+    context: {
+      ...context,
+      translateTime: true
+    }
   })
   const asString = prettifyTime({
     log: { time: `${epoch}`, msg: 'foo' },
-    translateFormat: true
+    context: {
+      ...context,
+      translateTime: true
+    }
   })
   const invalid = prettifyTime({
     log: { time: '2 days ago', msg: 'foo' },
-    translateFormat: true
+    context: {
+      ...context,
+      translateTime: true
+    }
   })
   t.same(asString, '[17:35:28.992]')
   t.same(asNumber, '[17:35:28.992]')
@@ -104,8 +214,13 @@ tap.test('works with epoch as a number or string', (t) => {
 tap.test('uses custom prettifier', async t => {
   const str = prettifyTime({
     log: { time: 0 },
-    prettifier () {
-      return 'done'
+    context: {
+      ...context,
+      customPrettifiers: {
+        time () {
+          return 'done'
+        }
+      }
     }
   })
   t.equal(str, 'done')
diff --git a/test/error-objects.test.js b/test/error-objects.test.js
index 15c11d32..e92e7ddf 100644
--- a/test/error-objects.test.js
+++ b/test/error-objects.test.js
@@ -313,7 +313,7 @@ test('error like objects tests', (t) => {
       '    statusCode: 500',
       '    originalStack: original stack',
       '    dataBaseSpecificError: {',
-      '        erroMessage: "some database error message"',
+      '        errorMessage: "some database error message"',
       '        evenMoreSpecificStuff: {',
       '          "someErrorRelatedObject": "error"',
       '        }',
@@ -331,7 +331,7 @@ test('error like objects tests', (t) => {
     error.statusCode = 500
     error.originalStack = 'original stack'
     error.dataBaseSpecificError = {
-      erroMessage: 'some database error message',
+      errorMessage: 'some database error message',
       evenMoreSpecificStuff: {
         someErrorRelatedObject: 'error'
       }