-
Notifications
You must be signed in to change notification settings - Fork 17
/
index.js
181 lines (163 loc) Β· 4.82 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
'use strict'
var DEFAULT_CENSOR = '[Redacted]'
var rxProp = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(\.|\[\])(?:\4|$))/g
function noir (serializers, keys, censor) {
if (Array.isArray(serializers)) {
censor = arguments.length === 2 ? keys : DEFAULT_CENSOR
keys = serializers
serializers = {}
} else if (arguments.length === 2) {
censor = DEFAULT_CENSOR
}
var shape = keys.map(strToPath).reduce(function (o, p) {
var k = p[0]
if (!o[k]) o[k] = []
o[k].push(p)
return o
}, {})
Object.keys(shape).forEach(function (top) {
if (shape[top].some(function (a) { return a.length === 1 })) {
shape[top] = redact
} else {
shape[top].forEach(function (a) { a.shift() })
shape[top] = factory(shape[top])
}
var prev = shape[top]
var serializer = serializers[top]
if (serializer) {
shape[top] = function (obj) {
var intermediate = serializer(obj)
return prev(intermediate)
}
}
})
Object.keys(serializers).forEach(function (top) {
if (!shape[top]) {
shape[top] = serializers[top]
}
})
return shape
function redact () { return {toJSON: mask} }
function mask (key) { return typeof censor === 'function' ? censor(this[key]) : censor }
// we use eval to pre-compile the redactor function
// this gives us up 100's of ms (per 10000ops) in some
// cases (deep nesting, wildcards). This is certainly
// safe in this case, there is not user input here.
/* eslint no-eval: 0 */
function factory (paths) {
var redactor
eval('redactor = function redactor (o) { return redact(o, ' + JSON.stringify(paths) + ') }')
redact() // shh linter
return redactor
// redact is too big to inline,
// can be made smaller by making unreadable,
// but doesn't seem to improve benchmarks
function redact (o, paths) {
if (o == null) return o
var i = 0
var len = paths.length
var path
var val
var key
var k
var parent
var pathIndex
var terminus
var cur
var pre
var red
for (; i < len; i++) {
path = paths[i]
pathIndex = 0
terminus = path.length - 1
key = path[terminus]
cur = o
while (cur != null && pathIndex < terminus) {
k = path[pathIndex++]
pre = cur
cur = cur[k]
}
parent = (pathIndex && pathIndex === terminus) ? cur : o
if (!parent) continue
k = path[pathIndex]
if (k === '*') {
red = redactAll(parent, censor)
if (red && parent === o) o = censor
else if (red) {
path.length = pathIndex
set(o, path, new Redacted(val, path[pathIndex - 1], pre, censor))
}
continue
}
val = parent[k]
if (val === undefined) { continue }
set(o, path, new Redacted(val, key, parent, censor))
}
return o
}
}
}
function redactAll (object, censor) {
if (typeof object !== 'object') {
return true
}
Object.keys(object).reduce(function (o, k) {
o[k] = new Redacted(o[k], k, object, censor)
return o
}, object)
}
// set was a hot path and bottleneck,
// had to make the function small enough
// to inline, hence the algebra.
// o = object, p = path, v = value,
// i = index, l = length, li = lastIndex,
// n = nested, k = key, nv = newValue, ov = objValue
/* eslint no-self-compare: 0, no-mixed-operators: 0 */
function set (o, p, v) {
var i = -1
var l = p.length
var li = l - 1
var n = o
var k
var nv
var ov
while (n != null && ++i < l) {
k = p[i]
nv = v
ov = n[k]
nv = (i !== li) ? ov : nv
n[k] = (objectHasProp(n, k) && nv === ov) || nv === undefined ? n[k] : nv
n = n[k]
}
return o
}
// any object created with prototype = null will crash server
// use objectHasProp to use Object hasOwnProperty method
function objectHasProp (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop)
}
function strToPath (s) {
var result = []
;(s + '').replace(rxProp, function (match, number) {
result.push(number || match)
})
return result
}
// the returned object is immediately JSON.stringified,
// https://github.com/mcollina/pino/blob/master/pino.js#L224-L225
// this means we can leverage the JSON.stringify loop
// print redacted, and replace with original value (cheap),
// instead of copying the object and stripping values (expensive)
// the Redacted constructor is used because it allows for
// quick triggering of hidden class optimization
function Redacted (val, key, parent, censor) {
this.val = val
this.key = key
this.parent = parent
this.censor = censor
}
Redacted.prototype.toJSON = function toJSON () {
this.parent[this.key] = this.val
return typeof this.censor === 'function' ? this.censor(this.val) : this.censor
}
module.exports = noir