-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtransport.js
108 lines (97 loc) · 2.76 KB
/
transport.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
import SonicBoom from 'sonic-boom'
import { once } from 'node:events'
import build from 'pino-abstract-transport'
import logfmt from 'logfmt'
import { snakeCase } from 'case-anything'
import { treeToKeyValue } from './tree-to-key-value.js'
import dateFormat from 'dateformat'
/**
*
* @type {Record<number, string>}
*/
export const baseLevelToLabel = {
10: 'trace',
20: 'debug',
30: 'info',
40: 'warn',
50: 'error',
60: 'fatal'
}
/**
* Escapes multi-line strings in an object, recursively and in place.
*
* @param {Record<unknown, unknown>} object
* @returns {Record<string, unknown>}
*/
function deeplyEscapeMultilineStringsInObject (obj) {
for (const field in obj) {
if (typeof obj[field] === 'string') {
obj[field] = obj[field].replace(/\n/g, '\\n')
} else if (typeof obj[field] === 'object') {
deeplyEscapeMultilineStringsInObject(obj[field])
}
}
}
/**
*
* @param {LogFmtTransportOptions} opts
* @returns {Promise<Transform & build.OnUnknown>}
*/
export default async function (opts = {}) {
const {
includeLevelLabel,
levelLabelKey = 'level_label',
formatTime,
timeKey = 'time',
convertToSnakeCase,
flattenNestedObjects,
flattenNestedSeparator,
customLevels = baseLevelToLabel,
timeFormat = 'isoDateTime',
escapeMultilineStrings
} = opts
// SonicBoom is necessary to avoid loops with the main thread.
// It is the same of pino.destination().
const destination = new SonicBoom({
dest: opts.destination || 1,
sync: opts.sync ?? false
})
await once(destination, 'ready')
return build(async function (source) {
for await (let obj of source) {
if (includeLevelLabel === true) {
obj[levelLabelKey] = customLevels[obj.level] ?? 'unknown'
}
if (formatTime === true) {
obj[timeKey] = dateFormat(obj[timeKey], timeFormat)
}
// treeToKeyValue transforms the field name to snake case
// if flattenNestedObjects is enabled.
if (convertToSnakeCase === true && flattenNestedObjects !== true) {
for (const field in obj) {
const snakeCaseName = snakeCase(field)
if (snakeCaseName !== field) {
obj[snakeCase(field)] = obj[field]
delete obj[field]
}
}
}
if (escapeMultilineStrings === true) {
deeplyEscapeMultilineStringsInObject(obj)
}
if (flattenNestedObjects === true) {
obj = treeToKeyValue(obj, flattenNestedSeparator, convertToSnakeCase)
}
const toDrain = !destination.write(logfmt.stringify(obj) + '\n')
// This block will handle backpressure
if (toDrain) {
await once(destination, 'drain')
}
}
}, {
async close () {
destination.end()
await once(destination, 'close')
}
})
}