1 | 'use strict'
|
2 |
|
3 |
|
4 |
|
5 | const format = require('quick-format-unescaped')
|
6 | const { mapHttpRequest, mapHttpResponse } = require('pino-std-serializers')
|
7 | const SonicBoom = require('sonic-boom')
|
8 | const stringifySafe = require('fast-safe-stringify')
|
9 | const {
|
10 | lsCacheSym,
|
11 | chindingsSym,
|
12 | parsedChindingsSym,
|
13 | writeSym,
|
14 | serializersSym,
|
15 | formatOptsSym,
|
16 | endSym,
|
17 | stringifiersSym,
|
18 | stringifySym,
|
19 | wildcardFirstSym,
|
20 | needsMetadataGsym,
|
21 | redactFmtSym,
|
22 | streamSym,
|
23 | nestedKeySym,
|
24 | formattersSym,
|
25 | messageKeySym
|
26 | } = require('./symbols')
|
27 |
|
28 | function noop () {}
|
29 |
|
30 | function genLog (level, hook) {
|
31 | if (!hook) return LOG
|
32 |
|
33 | return function hookWrappedLog (...args) {
|
34 | hook.call(this, args, LOG)
|
35 | }
|
36 |
|
37 | function LOG (o, ...n) {
|
38 | if (typeof o === 'object') {
|
39 | var msg = o
|
40 | if (o !== null) {
|
41 | if (o.method && o.headers && o.socket) {
|
42 | o = mapHttpRequest(o)
|
43 | } else if (typeof o.setHeader === 'function') {
|
44 | o = mapHttpResponse(o)
|
45 | }
|
46 | }
|
47 | if (this[nestedKeySym]) o = { [this[nestedKeySym]]: o }
|
48 | var formatParams
|
49 | if (msg === null && n.length === 0) {
|
50 | formatParams = [null]
|
51 | } else {
|
52 | msg = n.shift()
|
53 | formatParams = n
|
54 | }
|
55 | this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level)
|
56 | } else {
|
57 | this[writeSym](null, format(o, n, this[formatOptsSym]), level)
|
58 | }
|
59 | }
|
60 | }
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | function asString (str) {
|
68 | var result = ''
|
69 | var last = 0
|
70 | var found = false
|
71 | var point = 255
|
72 | const l = str.length
|
73 | if (l > 100) {
|
74 | return JSON.stringify(str)
|
75 | }
|
76 | for (var i = 0; i < l && point >= 32; i++) {
|
77 | point = str.charCodeAt(i)
|
78 | if (point === 34 || point === 92) {
|
79 | result += str.slice(last, i) + '\\'
|
80 | last = i
|
81 | found = true
|
82 | }
|
83 | }
|
84 | if (!found) {
|
85 | result = str
|
86 | } else {
|
87 | result += str.slice(last)
|
88 | }
|
89 | return point < 32 ? JSON.stringify(str) : '"' + result + '"'
|
90 | }
|
91 |
|
92 | function asJson (obj, msg, num, time) {
|
93 | const stringify = this[stringifySym]
|
94 | const stringifiers = this[stringifiersSym]
|
95 | const end = this[endSym]
|
96 | const chindings = this[chindingsSym]
|
97 | const serializers = this[serializersSym]
|
98 | const formatters = this[formattersSym]
|
99 | const messageKey = this[messageKeySym]
|
100 | var data = this[lsCacheSym][num] + time
|
101 |
|
102 |
|
103 |
|
104 | data = data + chindings
|
105 |
|
106 | var value
|
107 | var notHasOwnProperty = obj.hasOwnProperty === undefined
|
108 | if (formatters.log) {
|
109 | obj = formatters.log(obj)
|
110 | }
|
111 | if (msg !== undefined) {
|
112 | obj[messageKey] = msg
|
113 | }
|
114 | const wildcardStringifier = stringifiers[wildcardFirstSym]
|
115 | for (var key in obj) {
|
116 | value = obj[key]
|
117 | if ((notHasOwnProperty || obj.hasOwnProperty(key)) && value !== undefined) {
|
118 | value = serializers[key] ? serializers[key](value) : value
|
119 |
|
120 | const stringifier = stringifiers[key] || wildcardStringifier
|
121 |
|
122 | switch (typeof value) {
|
123 | case 'undefined':
|
124 | case 'function':
|
125 | continue
|
126 | case 'number':
|
127 |
|
128 | if (Number.isFinite(value) === false) {
|
129 | value = null
|
130 | }
|
131 |
|
132 | case 'boolean':
|
133 | if (stringifier) value = stringifier(value)
|
134 | break
|
135 | case 'string':
|
136 | value = (stringifier || asString)(value)
|
137 | break
|
138 | default:
|
139 | value = (stringifier || stringify)(value)
|
140 | }
|
141 | if (value === undefined) continue
|
142 | data += ',"' + key + '":' + value
|
143 | }
|
144 | }
|
145 |
|
146 | return data + end
|
147 | }
|
148 |
|
149 | function asChindings (instance, bindings) {
|
150 | var key
|
151 | var value
|
152 | var data = instance[chindingsSym]
|
153 | const stringify = instance[stringifySym]
|
154 | const stringifiers = instance[stringifiersSym]
|
155 | const serializers = instance[serializersSym]
|
156 | const formatter = instance[formattersSym].bindings
|
157 | bindings = formatter(bindings)
|
158 |
|
159 | for (key in bindings) {
|
160 | value = bindings[key]
|
161 | const valid = key !== 'level' &&
|
162 | key !== 'serializers' &&
|
163 | key !== 'formatters' &&
|
164 | key !== 'customLevels' &&
|
165 | bindings.hasOwnProperty(key) &&
|
166 | value !== undefined
|
167 | if (valid === true) {
|
168 | value = serializers[key] ? serializers[key](value) : value
|
169 | value = (stringifiers[key] || stringify)(value)
|
170 | if (value === undefined) continue
|
171 | data += ',"' + key + '":' + value
|
172 | }
|
173 | }
|
174 | return data
|
175 | }
|
176 |
|
177 | function getPrettyStream (opts, prettifier, dest, instance) {
|
178 | if (prettifier && typeof prettifier === 'function') {
|
179 | prettifier = prettifier.bind(instance)
|
180 | return prettifierMetaWrapper(prettifier(opts), dest)
|
181 | }
|
182 | try {
|
183 | var prettyFactory = require('pino-pretty')
|
184 | prettyFactory.asMetaWrapper = prettifierMetaWrapper
|
185 | return prettifierMetaWrapper(prettyFactory(opts), dest)
|
186 | } catch (e) {
|
187 | throw Error('Missing `pino-pretty` module: `pino-pretty` must be installed separately')
|
188 | }
|
189 | }
|
190 |
|
191 | function prettifierMetaWrapper (pretty, dest) {
|
192 | var warned = false
|
193 | return {
|
194 | [needsMetadataGsym]: true,
|
195 | lastLevel: 0,
|
196 | lastMsg: null,
|
197 | lastObj: null,
|
198 | lastLogger: null,
|
199 | flushSync () {
|
200 | if (warned) {
|
201 | return
|
202 | }
|
203 | warned = true
|
204 | setMetadataProps(dest, this)
|
205 | dest.write(pretty(Object.assign({
|
206 | level: 40,
|
207 | msg: 'pino.final with prettyPrint does not support flushing',
|
208 | time: Date.now()
|
209 | }, this.chindings())))
|
210 | },
|
211 | chindings () {
|
212 | const lastLogger = this.lastLogger
|
213 | var chindings = null
|
214 |
|
215 |
|
216 |
|
217 | if (!lastLogger) {
|
218 | return null
|
219 | }
|
220 |
|
221 | if (lastLogger.hasOwnProperty(parsedChindingsSym)) {
|
222 | chindings = lastLogger[parsedChindingsSym]
|
223 | } else {
|
224 | chindings = JSON.parse('{' + lastLogger[chindingsSym].substr(1) + '}')
|
225 | lastLogger[parsedChindingsSym] = chindings
|
226 | }
|
227 |
|
228 | return chindings
|
229 | },
|
230 | write (chunk) {
|
231 | const lastLogger = this.lastLogger
|
232 | const chindings = this.chindings()
|
233 |
|
234 | var time = this.lastTime
|
235 |
|
236 | if (time.match(/^\d+/)) {
|
237 | time = parseInt(time)
|
238 | }
|
239 |
|
240 | var lastObj = this.lastObj
|
241 | var errorProps = null
|
242 |
|
243 | const obj = Object.assign({
|
244 | level: this.lastLevel,
|
245 | time
|
246 | }, lastObj, errorProps)
|
247 |
|
248 | const serializers = lastLogger[serializersSym]
|
249 | const keys = Object.keys(serializers)
|
250 | var key
|
251 |
|
252 | for (var i = 0; i < keys.length; i++) {
|
253 | key = keys[i]
|
254 | if (obj[key] !== undefined) {
|
255 | obj[key] = serializers[key](obj[key])
|
256 | }
|
257 | }
|
258 |
|
259 | for (key in chindings) {
|
260 | if (!obj.hasOwnProperty(key)) {
|
261 | obj[key] = chindings[key]
|
262 | }
|
263 | }
|
264 |
|
265 | const stringifiers = lastLogger[stringifiersSym]
|
266 | const redact = stringifiers[redactFmtSym]
|
267 |
|
268 | const formatted = pretty(typeof redact === 'function' ? redact(obj) : obj)
|
269 | if (formatted === undefined) return
|
270 |
|
271 | setMetadataProps(dest, this)
|
272 | dest.write(formatted)
|
273 | }
|
274 | }
|
275 | }
|
276 |
|
277 | function hasBeenTampered (stream) {
|
278 | return stream.write !== stream.constructor.prototype.write
|
279 | }
|
280 |
|
281 | function buildSafeSonicBoom (opts) {
|
282 | const stream = new SonicBoom(opts)
|
283 | stream.on('error', filterBrokenPipe)
|
284 | return stream
|
285 |
|
286 | function filterBrokenPipe (err) {
|
287 |
|
288 | if (err.code === 'EPIPE') {
|
289 |
|
290 |
|
291 |
|
292 | stream.write = noop
|
293 | stream.end = noop
|
294 | stream.flushSync = noop
|
295 | stream.destroy = noop
|
296 | return
|
297 | }
|
298 | stream.removeListener('error', filterBrokenPipe)
|
299 | stream.emit('error', err)
|
300 | }
|
301 | }
|
302 |
|
303 | function createArgsNormalizer (defaultOptions) {
|
304 | return function normalizeArgs (instance, opts = {}, stream) {
|
305 |
|
306 | if (typeof opts === 'string') {
|
307 | stream = buildSafeSonicBoom({ dest: opts })
|
308 | opts = {}
|
309 | } else if (typeof stream === 'string') {
|
310 | stream = buildSafeSonicBoom({ dest: stream })
|
311 | } else if (opts instanceof SonicBoom || opts.writable || opts._writableState) {
|
312 | stream = opts
|
313 | opts = null
|
314 | }
|
315 | opts = Object.assign({}, defaultOptions, opts)
|
316 | if ('extreme' in opts) {
|
317 | throw Error('The extreme option has been removed, use pino.extreme instead')
|
318 | }
|
319 | if ('onTerminated' in opts) {
|
320 | throw Error('The onTerminated option has been removed, use pino.final instead')
|
321 | }
|
322 | if ('changeLevelName' in opts) {
|
323 | process.emitWarning(
|
324 | 'The changeLevelName option is deprecated and will be removed in v7. Use levelKey instead.',
|
325 | { code: 'changeLevelName_deprecation' }
|
326 | )
|
327 | opts.levelKey = opts.changeLevelName
|
328 | delete opts.changeLevelName
|
329 | }
|
330 | const { enabled, prettyPrint, prettifier, messageKey } = opts
|
331 | if (enabled === false) opts.level = 'silent'
|
332 | stream = stream || process.stdout
|
333 | if (stream === process.stdout && stream.fd >= 0 && !hasBeenTampered(stream)) {
|
334 | stream = buildSafeSonicBoom({ fd: stream.fd })
|
335 | }
|
336 | if (prettyPrint) {
|
337 | const prettyOpts = Object.assign({ messageKey }, prettyPrint)
|
338 | stream = getPrettyStream(prettyOpts, prettifier, stream, instance)
|
339 | }
|
340 | return { opts, stream }
|
341 | }
|
342 | }
|
343 |
|
344 | function final (logger, handler) {
|
345 | if (typeof logger === 'undefined' || typeof logger.child !== 'function') {
|
346 | throw Error('expected a pino logger instance')
|
347 | }
|
348 | const hasHandler = (typeof handler !== 'undefined')
|
349 | if (hasHandler && typeof handler !== 'function') {
|
350 | throw Error('if supplied, the handler parameter should be a function')
|
351 | }
|
352 | const stream = logger[streamSym]
|
353 | if (typeof stream.flushSync !== 'function') {
|
354 | throw Error('final requires a stream that has a flushSync method, such as pino.destination and pino.extreme')
|
355 | }
|
356 |
|
357 | const finalLogger = new Proxy(logger, {
|
358 | get: (logger, key) => {
|
359 | if (key in logger.levels.values) {
|
360 | return (...args) => {
|
361 | logger[key](...args)
|
362 | stream.flushSync()
|
363 | }
|
364 | }
|
365 | return logger[key]
|
366 | }
|
367 | })
|
368 |
|
369 | if (!hasHandler) {
|
370 | return finalLogger
|
371 | }
|
372 |
|
373 | return (err = null, ...args) => {
|
374 | try {
|
375 | stream.flushSync()
|
376 | } catch (e) {
|
377 |
|
378 |
|
379 |
|
380 |
|
381 | }
|
382 | return handler(err, finalLogger, ...args)
|
383 | }
|
384 | }
|
385 |
|
386 | function stringify (obj) {
|
387 | try {
|
388 | return JSON.stringify(obj)
|
389 | } catch (_) {
|
390 | return stringifySafe(obj)
|
391 | }
|
392 | }
|
393 |
|
394 | function buildFormatters (level, bindings, log) {
|
395 | return {
|
396 | level,
|
397 | bindings,
|
398 | log
|
399 | }
|
400 | }
|
401 |
|
402 | function setMetadataProps (dest, that) {
|
403 | if (dest[needsMetadataGsym] === true) {
|
404 | dest.lastLevel = that.lastLevel
|
405 | dest.lastMsg = that.lastMsg
|
406 | dest.lastObj = that.lastObj
|
407 | dest.lastTime = that.lastTime
|
408 | dest.lastLogger = that.lastLogger
|
409 | }
|
410 | }
|
411 |
|
412 | module.exports = {
|
413 | noop,
|
414 | buildSafeSonicBoom,
|
415 | getPrettyStream,
|
416 | asChindings,
|
417 | asJson,
|
418 | genLog,
|
419 | createArgsNormalizer,
|
420 | final,
|
421 | stringify,
|
422 | buildFormatters
|
423 | }
|