Skip to content

Commit

Permalink
A new option for users to choose log keys to include (#373)
Browse files Browse the repository at this point in the history
* add include option

* add types to the new include option

* add new option include to cli

* tests for the new option include

* upate readme for the new option include

* set include to be undefined in defaultOptions

* update filterLog signature and jsdoc

* correct jsdoc of filterLog
  • Loading branch information
TommyDew42 committed Aug 24, 2022
1 parent a247e2c commit 0825842
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 31 deletions.
5 changes: 4 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ node app.js | pino-pretty
system time zone.
- `--ignore` (`-i`): Ignore one or several keys, nested keys are supported with each property delimited by a dot character (`.`),
keys may be escaped to target property names that contains the delimiter itself:
(`-i time,hostname,req.headers,log\\.domain\\.corp/foo`)
(`-i time,hostname,req.headers,log\\.domain\\.corp/foo`).
The `--ignore` option would be ignored, if both `--ignore` and `--include` are passed.
Default: `hostname`.
- `--include` (`-I`): The opposite of `--ignore`. Include one or several keys.
- `--hideObject` (`-H`): Hide objects from output (but not error object)
- `--singleLine` (`-S`): Print each log message on a single line (errors will still be multi-line)
- `--config`: Specify a path to a config file containing the pino-pretty options. pino-pretty will attempt to read from a `.pino-prettyrc` in your current directory (`process.cwd`) if not specified
Expand Down Expand Up @@ -243,6 +245,7 @@ The options accepted have keys corresponding to the options described in [CLI Ar
timestampKey: 'time', // --timestampKey
translateTime: false, // --translateTime
ignore: 'pid,hostname', // --ignore
include: 'level,time', // --include
hideObject: false, // --hideObject
singleLine: false, // --singleLine

Expand Down
1 change: 1 addition & 0 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ let opts = minimist(process.argv, {
timestampKey: 'a',
translateTime: 't',
ignore: 'i',
include: 'I',
hideObject: 'H',
singleLine: 'S'
},
Expand Down
4 changes: 4 additions & 0 deletions help/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
-h, --help Output usage information
-H, --hideObject Hide objects from output (but not error object)
-i, --ignore Ignore one or several keys: (`-i time,hostname`)
-I, --include The opposite of `--ignore`, only include one or several keys: (`-I level,time`)
-l, --levelFirst Display the log level as the first output field
-L, --levelKey [value] Detect the log level under the specified key (defaults to "level")
-b, --levelLabel [value] Output the log level using the specified label (defaults to "levelLabel")
Expand Down Expand Up @@ -57,6 +58,9 @@

- Prettify logs but don't print pid and hostname
$ cat log | pino-pretty -i pid,hostname

- Prettify logs but only print time and level
$ cat log | pino-pretty -i time,level

- Loads options from a config file
$ cat log | pino-pretty --config=/path/to/config.json
Expand Down
6 changes: 6 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,15 @@ interface PrettyOptions_ {
errorProps?: string;
/**
* Ignore one or several keys.
* Will be overridden by the option include if include is presented.
* @example "time,hostname"
*/
ignore?: string;
/**
* Include one or several keys.
* @example "time,level"
*/
include?: string;
/**
* Makes messaging synchronous.
* @default false
Expand Down
8 changes: 5 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const defaultOptions = {
customPrettifiers: {},
hideObject: false,
ignore: 'hostname',
include: undefined,
singleLine: false
}

Expand Down Expand Up @@ -107,7 +108,8 @@ function prettyFactory (options) {
customProps.customLevelNames = undefined
}
const customPrettifiers = opts.customPrettifiers
const ignoreKeys = opts.ignore ? new Set(opts.ignore.split(',')) : undefined
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)
Expand Down Expand Up @@ -136,8 +138,8 @@ function prettyFactory (options) {

const prettifiedMessage = prettifyMessage({ log, messageKey, colorizer, messageFormat, levelLabel, ...customProps, useOnlyCustomProps })

if (ignoreKeys) {
log = filterLog(log, ignoreKeys)
if (ignoreKeys || includeKeys) {
log = filterLog({ log, ignoreKeys, includeKeys })
}

const prettifiedLevel = prettifyLevel({ log, colorizer, levelKey, prettifier: customPrettifiers.level, ...customProps })
Expand Down
28 changes: 23 additions & 5 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -571,15 +571,33 @@ function deleteLogProperty (log, property) {
}

/**
* Filter a log object by removing any ignored keys.
* 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} log The log object to be modified.
* @param {Set<string> | Array<string>} ignoreKeys An array of strings identifying the properties to be removed.
* @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.
*
* @returns {object} A new `log` object instance that does not include the ignored keys.
* @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) {
function filterLog ({ log, ignoreKeys, includeKeys }) {
const logCopy = fastCopy(log)

if (includeKeys) {
const logIncluded = {}

includeKeys.forEach((key) => {
logIncluded[key] = logCopy[key]
})
return logIncluded
}

ignoreKeys.forEach((ignoreKey) => {
deleteLogProperty(logCopy, ignoreKey)
})
Expand Down
28 changes: 28 additions & 0 deletions test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,34 @@ test('basic prettifier tests', (t) => {
t.equal(arst, 'hello world\n')
})

t.test('include nothing', (t) => {
t.plan(1)
const pretty = prettyFactory({ include: '' })
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
t.equal(arst, 'hello world\n')
})

t.test('include multiple keys', (t) => {
t.plan(1)
const pretty = prettyFactory({ include: 'time,level' })
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
t.equal(arst, `[${formattedEpoch}] INFO: hello world\n`)
})

t.test('include a single key', (t) => {
t.plan(1)
const pretty = prettyFactory({ include: 'level' })
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
t.equal(arst, 'INFO: hello world\n')
})

t.test('include should override ignore', (t) => {
t.plan(1)
const pretty = prettyFactory({ ignore: 'time,level', include: 'time,level' })
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
t.equal(arst, `[${formattedEpoch}] INFO: hello world\n`)
})

t.test('prettifies trace caller', (t) => {
t.plan(1)
const traceCaller = (instance) => {
Expand Down
94 changes: 72 additions & 22 deletions test/lib/utils.public.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,52 +387,86 @@ tap.test('prettifyTime', t => {
t.end()
})

tap.test('#filterLog', t => {
const { filterLog } = utils
const logData = {
level: 30,
time: 1522431328992,
data1: {
data2: { 'data-3': 'bar' },
error: new Error('test')
}
const logData = {
level: 30,
time: 1522431328992,
data1: {
data2: { 'data-3': 'bar' },
error: new Error('test')
}
}
const logData2 = Object.assign({
'logging.domain.corp/operation': {
id: 'foo',
producer: 'bar'
}
}, logData)

tap.test('#filterLog with an ignoreKeys option', t => {
const { filterLog } = utils

t.test('filterLog removes single entry', async t => {
const result = filterLog(logData, ['data1.data2.data-3'])
const result = filterLog({ log: logData, 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(logData, ['time', 'data1'])
const result = filterLog({ log: logData, ignoreKeys: ['time', 'data1'] })
t.same(result, { level: 30 })
})

t.test('filterLog keeps error instance', async t => {
const result = filterLog(logData, [])
const result = filterLog({ log: logData, ignoreKeys: [] })
t.equal(logData.data1.error, result.data1.error)
})

const logData2 = Object.assign({
'logging.domain.corp/operation': {
id: 'foo',
producer: 'bar'
}
}, logData)

t.test('filterLog removes entry with escape sequence', async t => {
const result = filterLog(logData2, ['data1', 'logging\\.domain\\.corp/operation'])
const result = filterLog({ log: logData2, 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(logData2, ['data1', 'logging\\.domain\\.corp/operation.producer'])
const result = filterLog({ log: logData2, ignoreKeys: ['data1', 'logging\\.domain\\.corp/operation.producer'] })
t.same(result, { level: 30, time: 1522431328992, 'logging.domain.corp/operation': { id: 'foo' } })
})

t.end()
})

const ignoreKeysArray = [
undefined,
['level'],
['level', 'data1.data2.data-3']
]
ignoreKeysArray.forEach(ignoreKeys => {
tap.test(`#filterLog with an includeKeys option when the ignoreKeys being ${ignoreKeys}`, t => {
const { filterLog } = utils

t.test('filterLog include nothing', async t => {
const result = filterLog({ log: logData, ignoreKeys, includeKeys: [] })
t.same(result, {})
})

t.test('filterLog include single entry', async t => {
const result = filterLog({ log: logData, 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'] })
t.same(result, {
time: 1522431328992,
data1: {
data2: { 'data-3': 'bar' },
error: new Error('test')
}
})
})

t.end()
})
})

tap.test('#filterLog with circular references', t => {
const { filterLog } = utils
const logData = {
Expand All @@ -443,7 +477,7 @@ tap.test('#filterLog with circular references', t => {
logData.circular = logData

t.test('filterLog removes single entry', async t => {
const result = filterLog(logData, ['data1'])
const result = filterLog({ log: logData, ignoreKeys: ['data1'] })

t.same(result.circular.level, result.level)
t.same(result.circular.time, result.time)
Expand All @@ -452,6 +486,22 @@ tap.test('#filterLog with circular references', t => {
t.same(result, { level: 30, time: 1522431328992 })
})

t.test('filterLog includes single entry', async t => {
const result = filterLog({ log: logData, includeKeys: ['data1'] })

t.same(result, { data1: 'test' })
})

t.test('filterLog includes circular keys', async t => {
const result = filterLog({ log: logData, includeKeys: ['level', 'circular'] })

t.same(result.circular.level, logData.level)
t.same(result.circular.time, logData.time)

delete result.circular
t.same(result, { level: 30 })
})

t.end()
})

Expand Down

0 comments on commit 0825842

Please sign in to comment.