Repository: pinojs/pino (js)

Issue:
Exception thrown when redacting objects paths that contain a hyphen
Using `pino@5.0.1` and `fast-redact@1.1.13`.

This seems to be an issue specifically w/ the `pino` validation check, as the below test script shows that redaction via `fast-redact` works while the same arguments in `pino` will throw an exception:

```
// test fast-redact and hyphenated object path
const fastRedact = require('fast-redact')
const fauxRequest = {
  headers: {
    'x-access-token': 'super-secret',
    host: 'http://example.com',
    cookie: `oh oh we don't want this exposed in logs in etc.`,
    referer: `if we're cool maybe we'll even redact this`
  }
}
const redact = fastRedact({
  paths: ['headers.cookie', 'headers.referer', 'headers["x-access-token"]']
})

console.log('\n');
console.log(redact(fauxRequest))
console.log('\n');

// test pino instantiation w/ the same arguments for redaction
const logger = require('pino')({ redact: { paths: ['headers.cookie', 'headers.referer', 'headers["x-access-token"]' ] } });
```

-->

```

{"headers":{"x-access-token":"[REDACTED]","host":"http://example.com","cookie":"[REDACTED]","referer":"[REDACTED]"}}


/home/austin/code/common/node_modules/fast-redact/lib/validator.js:38
        throw Error(ERR_INVALID_PATH(s))
        ^

Error: fast-redact – Invalid path ("x-access-token"])
    at paths.forEach (/home/austin/code/common/node_modules/fast-redact/lib/validator.js:38:15)
    at Array.forEach (<anonymous>)
    at validate (/home/austin/code/common/node_modules/fast-redact/lib/validator.js:18:11)
    at fastRedact (/home/austin/code/common/node_modules/fast-redact/index.js:36:3)
    at Object.keys.reduce (/home/austin/code/common/node_modules/pino/lib/redaction.js:44:17)
    at Array.reduce (<anonymous>)
    at redaction (/home/austin/code/common/node_modules/pino/lib/redaction.js:41:29)
    at pino (/home/austin/code/common/node_modules/pino/pino.js:65:33)
    at Object.<anonymous> (/home/austin/code/common/scratch.js:18:31)
    at Module._compile (module.js:652:30)

```

Pull request:
Redaction fix - closes #480
includes extra documentation


Patch:
diff --git a/docs/api.md b/docs/api.md
index 2dfb67a..d76efd7 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -82,7 +82,7 @@ have their values redacted from any log output.
 Each path must be a string using a syntax which corresponds to JavaScript dot and bracket notation.
 
 If an object is supplied, three options can be specified: 
-  * `paths` (array): Required. An array of paths
+  * `paths` (array): Required. An array of paths. See [redaction - Path Syntax ⇗](/docs/redaction.md#paths) for specifics.
   * `censor` (String): Optional. A value to overwrite key which are to be redacted. Default: `'[Redacted]'` 
   * `remove` (Boolean): Optional. Instead of censoring the value, remove both the key and the value. Default: `false`
 
diff --git a/docs/redaction.md b/docs/redaction.md
index db99e4d..2ba5b01 100644
--- a/docs/redaction.md
+++ b/docs/redaction.md
@@ -93,6 +93,23 @@ This will output
 
 See [pino options in API](api.md#pino) for `redact` API details.
 
+<a name="paths"></a>
+## Path Syntax
+
+The syntax for paths supplied to the `redact` option conform to the syntax in path lookups
+in standard EcmaScript, with two additions: 
+
+* paths may start with bracket notation
+* paths may contain the asterisk `*` to denote a wildcard
+
+By way of example, the following are all valid paths:
+
+* `a.b.c`
+* `a["b-c"].d`
+* `["a-b"].c`
+* `a.b.*`
+* `a[*].b`
+
 ## Overhead
 
 Pino's redaction functionality is built on top of [`fast-redact`](http://github.com/davidmarkclements/fast-redact)
diff --git a/lib/redaction.js b/lib/redaction.js
index 0df60cf..dfdb68c 100644
--- a/lib/redaction.js
+++ b/lib/redaction.js
@@ -25,8 +25,11 @@ function redaction (opts, serialize) {
       return o
     }
     const { index } = next
-    o[str.substr(0, index - 1)] = o[str.substr(0, index - 1)] || []
-    o[str.substr(0, index - 1)].push(str.substr(index, str.length - 1))
+    const firstPosBracket = str[index - 1] === '['
+    const leadingChar = firstPosBracket ? '[' : ''
+    const ns = str.substr(0, index - 1).replace(/^\["(.+)"\]$/, '$1')
+    o[ns] = o[ns] || []
+    o[ns].push(`${leadingChar}${str.substr(index, str.length - 1)}`)
     return o
   }, {})
 
diff --git a/package.json b/package.json
index 1187489..ae00861 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
   },
   "dependencies": {
     "fast-json-parse": "^1.0.3",
-    "fast-redact": "^1.1.13",
+    "fast-redact": "^1.1.14",
     "fast-safe-stringify": "^2.0.4",
     "flatstr": "^1.0.5",
     "pino-std-serializers": "^2.2.0",


Test patch:
diff --git a/test/redact.test.js b/test/redact.test.js
index d484498..61cf96c 100644
--- a/test/redact.test.js
+++ b/test/redact.test.js
@@ -415,3 +415,25 @@ test('redacts null at the top level', async ({is}) => {
   const o = await once(stream, 'data')
   is(o.n, '[Redacted]')
 })
+
+test('supports bracket notation', async ({is}) => {
+  const stream = sink()
+  const instance = pino({redact: ['a["b-b"]']}, stream)
+  const obj = {
+    a: {'b-b': 'c'}
+  }
+  instance.info(obj)
+  const o = await once(stream, 'data')
+  is(o.a['b-b'], '[Redacted]')
+})
+
+test('supports leading bracket notation', async ({is}) => {
+  const stream = sink()
+  const instance = pino({redact: ['["a-a"].b']}, stream)
+  const obj = {
+    'a-a': {b: 'c'}
+  }
+  instance.info(obj)
+  const o = await once(stream, 'data')
+  is(o['a-a'].b, '[Redacted]')
+})