@mhmdhammoud/meritt-utils 1.5.8 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -94,7 +94,7 @@ describe('route and format logs', () => {
|
|
|
94
94
|
key0: 'val0',
|
|
95
95
|
key1: 'val1',
|
|
96
96
|
};
|
|
97
|
-
test('log info with structured context (single object
|
|
97
|
+
test('log info with structured context (single object in context field)', () => {
|
|
98
98
|
//@ts-ignore
|
|
99
99
|
jest.spyOn(pino_1.pino, 'destination').mockReturnValue(PINO_DESTINATION);
|
|
100
100
|
//@ts-ignore
|
|
@@ -105,11 +105,10 @@ describe('route and format logs', () => {
|
|
|
105
105
|
component: LOGGER_NAME,
|
|
106
106
|
code: LOG_EVENT.code,
|
|
107
107
|
msg: LOG_EVENT.msg,
|
|
108
|
-
key0: 'val0',
|
|
109
|
-
key1: 'val1',
|
|
108
|
+
context: { key0: 'val0', key1: 'val1' },
|
|
110
109
|
}));
|
|
111
110
|
});
|
|
112
|
-
test('remap reserved elastic field names
|
|
111
|
+
test('remap reserved elastic field names in context', () => {
|
|
113
112
|
//@ts-ignore
|
|
114
113
|
jest.spyOn(pino_1.pino, 'destination').mockReturnValue(PINO_DESTINATION);
|
|
115
114
|
//@ts-ignore
|
|
@@ -122,10 +121,12 @@ describe('route and format logs', () => {
|
|
|
122
121
|
_index: 'bad-index',
|
|
123
122
|
},
|
|
124
123
|
});
|
|
125
|
-
//
|
|
124
|
+
// Context holds sanitized structure; reserved names remapped recursively
|
|
126
125
|
expect(PINO.info).toHaveBeenCalledWith(expect.objectContaining({
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
context: expect.objectContaining({
|
|
127
|
+
mongo_id: 'abc123',
|
|
128
|
+
nested: { mongo_id: 'nested-1', es_index: 'bad-index' },
|
|
129
|
+
}),
|
|
129
130
|
}));
|
|
130
131
|
expect(PINO.info).not.toHaveBeenCalledWith(expect.objectContaining({
|
|
131
132
|
_id: expect.anything(),
|
|
@@ -33,24 +33,12 @@ function getIndexName(index, time) {
|
|
|
33
33
|
}
|
|
34
34
|
return index.replace('%{DATE}', time.substring(0, 10));
|
|
35
35
|
}
|
|
36
|
-
function initializeBulkHandler(opts, client, splitter) {
|
|
36
|
+
function initializeBulkHandler(opts, client, splitter, onFatalError) {
|
|
37
37
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
38
38
|
const esVersion = Number((_b = (_a = opts.esVersion) !== null && _a !== void 0 ? _a : opts['es-version']) !== null && _b !== void 0 ? _b : 7);
|
|
39
39
|
const index = (_c = opts.index) !== null && _c !== void 0 ? _c : 'pino';
|
|
40
40
|
const buildIndexName = typeof index === 'function' ? index : null;
|
|
41
41
|
const opType = esVersion >= 7 ? undefined : undefined;
|
|
42
|
-
// CRITICAL FIX (issue #140): When bulk helper destroys stream after retries exhausted,
|
|
43
|
-
// we must BOTH resurrect the pool AND reinitialize the bulk handler so logging continues.
|
|
44
|
-
// connectionPool.resurrect exists at runtime (elastic-transport) but may not be in types
|
|
45
|
-
const pool = client.connectionPool;
|
|
46
|
-
const splitterWithDestroy = splitter;
|
|
47
|
-
splitterWithDestroy.destroy = function () {
|
|
48
|
-
if (typeof pool.resurrect === 'function') {
|
|
49
|
-
pool.resurrect({ name: 'elasticsearch-js' });
|
|
50
|
-
}
|
|
51
|
-
// Reinitialize bulk handler - without this, logging stops permanently until restart
|
|
52
|
-
initializeBulkHandler(opts, client, splitter);
|
|
53
|
-
};
|
|
54
42
|
const indexName = (time = new Date().toISOString()) => buildIndexName ? buildIndexName(time) : getIndexName(index, time);
|
|
55
43
|
const bulkInsert = client.helpers.bulk({
|
|
56
44
|
datasource: splitter,
|
|
@@ -77,7 +65,10 @@ function initializeBulkHandler(opts, client, splitter) {
|
|
|
77
65
|
splitter.emit('insertError', error);
|
|
78
66
|
},
|
|
79
67
|
});
|
|
80
|
-
bulkInsert.then((stats) => splitter.emit('insert', stats), (err) =>
|
|
68
|
+
bulkInsert.then((stats) => splitter.emit('insert', stats), (err) => {
|
|
69
|
+
splitter.emit('error', err);
|
|
70
|
+
onFatalError(err);
|
|
71
|
+
});
|
|
81
72
|
}
|
|
82
73
|
const createElasticTransport = (opts = {}) => {
|
|
83
74
|
const splitter = split(function (line) {
|
|
@@ -128,10 +119,56 @@ const createElasticTransport = (opts = {}) => {
|
|
|
128
119
|
clientOpts.ConnectionPool = opts.ConnectionPool;
|
|
129
120
|
}
|
|
130
121
|
const client = new elasticsearch_1.Client(clientOpts);
|
|
122
|
+
// CRITICAL FIX (pino-elasticsearch issues #140/#72): after retries are
|
|
123
|
+
// exhausted the bulk helper can stop consuming the stream while the process
|
|
124
|
+
// stays alive. Keep exactly one helper active and replace it after fatal
|
|
125
|
+
// helper failures instead of waiting for a server restart.
|
|
126
|
+
let isBulkHandlerActive = false;
|
|
127
|
+
let isRestartScheduled = false;
|
|
128
|
+
let isTransportClosed = false;
|
|
129
|
+
const pool = client.connectionPool;
|
|
130
|
+
const splitterWithDestroy = splitter;
|
|
131
|
+
const originalDestroy = splitterWithDestroy.destroy.bind(splitterWithDestroy);
|
|
132
|
+
const startBulkHandler = () => {
|
|
133
|
+
if (isTransportClosed || isBulkHandlerActive) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
isBulkHandlerActive = true;
|
|
137
|
+
initializeBulkHandler(opts, client, splitter, () => {
|
|
138
|
+
isBulkHandlerActive = false;
|
|
139
|
+
scheduleBulkHandlerRestart();
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
const scheduleBulkHandlerRestart = () => {
|
|
143
|
+
var _a, _b, _c;
|
|
144
|
+
if (isTransportClosed || isRestartScheduled) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
isRestartScheduled = true;
|
|
148
|
+
if (typeof pool.resurrect === 'function') {
|
|
149
|
+
pool.resurrect({ name: 'elasticsearch-js' });
|
|
150
|
+
}
|
|
151
|
+
const retryDelayMs = Math.min(Number((_b = (_a = opts.flushInterval) !== null && _a !== void 0 ? _a : opts['flush-interval']) !== null && _b !== void 0 ? _b : 3000), 5000);
|
|
152
|
+
const timer = setTimeout(() => {
|
|
153
|
+
isRestartScheduled = false;
|
|
154
|
+
startBulkHandler();
|
|
155
|
+
}, retryDelayMs);
|
|
156
|
+
(_c = timer.unref) === null || _c === void 0 ? void 0 : _c.call(timer);
|
|
157
|
+
};
|
|
158
|
+
splitterWithDestroy.destroy = function (err) {
|
|
159
|
+
if (err && !isTransportClosed) {
|
|
160
|
+
scheduleBulkHandlerRestart();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
isTransportClosed = true;
|
|
164
|
+
originalDestroy(err);
|
|
165
|
+
};
|
|
131
166
|
client.diagnostic.on('resurrect', () => {
|
|
132
|
-
|
|
167
|
+
if (!isBulkHandlerActive) {
|
|
168
|
+
scheduleBulkHandlerRestart();
|
|
169
|
+
}
|
|
133
170
|
});
|
|
134
|
-
|
|
171
|
+
startBulkHandler();
|
|
135
172
|
return splitter;
|
|
136
173
|
};
|
|
137
174
|
exports.createElasticTransport = createElasticTransport;
|
package/dist/lib/logger.js
CHANGED
|
@@ -77,37 +77,6 @@ const isPlainObject = (v) => v !== null &&
|
|
|
77
77
|
!Array.isArray(v) &&
|
|
78
78
|
!(v instanceof Error) &&
|
|
79
79
|
!(v instanceof Date);
|
|
80
|
-
/**
|
|
81
|
-
* Reduces object values to scalars for top-level ES fields.
|
|
82
|
-
* ES text/keyword fields reject nested objects; use _id or truncated JSON.
|
|
83
|
-
*/
|
|
84
|
-
const toScalarForTopLevel = (value) => {
|
|
85
|
-
if (value === null || value === undefined)
|
|
86
|
-
return null;
|
|
87
|
-
if (typeof value === 'string' ||
|
|
88
|
-
typeof value === 'number' ||
|
|
89
|
-
typeof value === 'boolean')
|
|
90
|
-
return value;
|
|
91
|
-
if (value instanceof Date)
|
|
92
|
-
return value.toISOString();
|
|
93
|
-
if (isObjectIdLike(value))
|
|
94
|
-
return value.toHexString();
|
|
95
|
-
if (value instanceof Error)
|
|
96
|
-
return value.message;
|
|
97
|
-
if (isPlainObject(value) && '_id' in value) {
|
|
98
|
-
const id = value._id;
|
|
99
|
-
if (id != null) {
|
|
100
|
-
if (typeof id === 'string')
|
|
101
|
-
return id;
|
|
102
|
-
if (isObjectIdLike(id))
|
|
103
|
-
return id.toHexString();
|
|
104
|
-
if (typeof id === 'object' && id !== null && 'toString' in id)
|
|
105
|
-
return String(id.toString());
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const str = typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
109
|
-
return str.length > 200 ? `${str.slice(0, 200)}...` : str;
|
|
110
|
-
};
|
|
111
80
|
/**
|
|
112
81
|
* Recursively sanitize log values for Elasticsearch safety.
|
|
113
82
|
* - Remaps reserved key names (e.g. `_id` -> `mongo_id`)
|
|
@@ -412,17 +381,15 @@ class Logger {
|
|
|
412
381
|
if (trace) {
|
|
413
382
|
ecs.trace_id = trace.traceId;
|
|
414
383
|
}
|
|
415
|
-
// Structured context:
|
|
416
|
-
//
|
|
384
|
+
// Structured context: put in single 'context' field to avoid ES mapping conflicts.
|
|
385
|
+
// Flattening to top-level caused document_parsing_exception (object vs scalar type mismatches).
|
|
386
|
+
// Nesting in context keeps structure consistent and avoids per-field mapping conflicts.
|
|
387
|
+
let context;
|
|
417
388
|
let detail;
|
|
418
389
|
if (args.length === 1 &&
|
|
419
390
|
isPlainObject(args[0]) &&
|
|
420
391
|
Object.keys(args[0]).length > 0) {
|
|
421
|
-
|
|
422
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
423
|
-
const key = toSafeElasticFieldName(k);
|
|
424
|
-
ecs[key] = toScalarForTopLevel(v);
|
|
425
|
-
}
|
|
392
|
+
context = sanitizeForElastic(args[0]);
|
|
426
393
|
}
|
|
427
394
|
else {
|
|
428
395
|
detail = isLocal ? args : JSON.stringify(sanitizeForElastic(args));
|
|
@@ -434,6 +401,9 @@ class Logger {
|
|
|
434
401
|
code: event.code,
|
|
435
402
|
msg: event.msg,
|
|
436
403
|
};
|
|
404
|
+
if (context !== undefined) {
|
|
405
|
+
base.context = context;
|
|
406
|
+
}
|
|
437
407
|
if (detail !== undefined) {
|
|
438
408
|
base.detail = detail;
|
|
439
409
|
}
|
package/package.json
CHANGED
|
@@ -70,7 +70,7 @@ describe('route and format logs', () => {
|
|
|
70
70
|
key0: 'val0',
|
|
71
71
|
key1: 'val1',
|
|
72
72
|
}
|
|
73
|
-
test('log info with structured context (single object
|
|
73
|
+
test('log info with structured context (single object in context field)', () => {
|
|
74
74
|
//@ts-ignore
|
|
75
75
|
jest.spyOn(pino, 'destination').mockReturnValue(PINO_DESTINATION)
|
|
76
76
|
//@ts-ignore
|
|
@@ -84,13 +84,12 @@ describe('route and format logs', () => {
|
|
|
84
84
|
component: LOGGER_NAME,
|
|
85
85
|
code: LOG_EVENT.code,
|
|
86
86
|
msg: LOG_EVENT.msg,
|
|
87
|
-
key0: 'val0',
|
|
88
|
-
key1: 'val1',
|
|
87
|
+
context: { key0: 'val0', key1: 'val1' },
|
|
89
88
|
})
|
|
90
89
|
)
|
|
91
90
|
})
|
|
92
91
|
|
|
93
|
-
test('remap reserved elastic field names
|
|
92
|
+
test('remap reserved elastic field names in context', () => {
|
|
94
93
|
//@ts-ignore
|
|
95
94
|
jest.spyOn(pino, 'destination').mockReturnValue(PINO_DESTINATION)
|
|
96
95
|
//@ts-ignore
|
|
@@ -105,11 +104,13 @@ describe('route and format logs', () => {
|
|
|
105
104
|
},
|
|
106
105
|
})
|
|
107
106
|
|
|
108
|
-
//
|
|
107
|
+
// Context holds sanitized structure; reserved names remapped recursively
|
|
109
108
|
expect(PINO.info).toHaveBeenCalledWith(
|
|
110
109
|
expect.objectContaining({
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
context: expect.objectContaining({
|
|
111
|
+
mongo_id: 'abc123',
|
|
112
|
+
nested: { mongo_id: 'nested-1', es_index: 'bad-index' },
|
|
113
|
+
}),
|
|
113
114
|
})
|
|
114
115
|
)
|
|
115
116
|
expect(PINO.info).not.toHaveBeenCalledWith(
|
|
@@ -76,30 +76,14 @@ function getIndexName(
|
|
|
76
76
|
function initializeBulkHandler(
|
|
77
77
|
opts: ElasticTransportOptions,
|
|
78
78
|
client: Client,
|
|
79
|
-
splitter: NodeJS.ReadWriteStream
|
|
79
|
+
splitter: NodeJS.ReadWriteStream,
|
|
80
|
+
onFatalError: (err: Error) => void
|
|
80
81
|
): void {
|
|
81
82
|
const esVersion = Number(opts.esVersion ?? opts['es-version'] ?? 7)
|
|
82
83
|
const index = opts.index ?? 'pino'
|
|
83
84
|
const buildIndexName = typeof index === 'function' ? index : null
|
|
84
85
|
const opType = esVersion >= 7 ? undefined : undefined
|
|
85
86
|
|
|
86
|
-
// CRITICAL FIX (issue #140): When bulk helper destroys stream after retries exhausted,
|
|
87
|
-
// we must BOTH resurrect the pool AND reinitialize the bulk handler so logging continues.
|
|
88
|
-
// connectionPool.resurrect exists at runtime (elastic-transport) but may not be in types
|
|
89
|
-
const pool = client.connectionPool as {
|
|
90
|
-
resurrect?: (opts: { name: string }) => void
|
|
91
|
-
}
|
|
92
|
-
const splitterWithDestroy = splitter as NodeJS.ReadWriteStream & {
|
|
93
|
-
destroy: (err?: Error) => void
|
|
94
|
-
}
|
|
95
|
-
splitterWithDestroy.destroy = function () {
|
|
96
|
-
if (typeof pool.resurrect === 'function') {
|
|
97
|
-
pool.resurrect({ name: 'elasticsearch-js' })
|
|
98
|
-
}
|
|
99
|
-
// Reinitialize bulk handler - without this, logging stops permanently until restart
|
|
100
|
-
initializeBulkHandler(opts, client, splitter)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
87
|
const indexName = (time = new Date().toISOString()) =>
|
|
104
88
|
buildIndexName ? buildIndexName(time) : getIndexName(index as string, time)
|
|
105
89
|
|
|
@@ -132,7 +116,10 @@ function initializeBulkHandler(
|
|
|
132
116
|
|
|
133
117
|
bulkInsert.then(
|
|
134
118
|
(stats) => splitter.emit('insert', stats),
|
|
135
|
-
(err) =>
|
|
119
|
+
(err) => {
|
|
120
|
+
splitter.emit('error', err)
|
|
121
|
+
onFatalError(err)
|
|
122
|
+
}
|
|
136
123
|
)
|
|
137
124
|
}
|
|
138
125
|
|
|
@@ -193,11 +180,70 @@ export const createElasticTransport = (
|
|
|
193
180
|
|
|
194
181
|
const client = new Client(clientOpts)
|
|
195
182
|
|
|
183
|
+
// CRITICAL FIX (pino-elasticsearch issues #140/#72): after retries are
|
|
184
|
+
// exhausted the bulk helper can stop consuming the stream while the process
|
|
185
|
+
// stays alive. Keep exactly one helper active and replace it after fatal
|
|
186
|
+
// helper failures instead of waiting for a server restart.
|
|
187
|
+
let isBulkHandlerActive = false
|
|
188
|
+
let isRestartScheduled = false
|
|
189
|
+
let isTransportClosed = false
|
|
190
|
+
|
|
191
|
+
const pool = client.connectionPool as {
|
|
192
|
+
resurrect?: (opts: { name: string }) => void
|
|
193
|
+
}
|
|
194
|
+
const splitterWithDestroy = splitter as NodeJS.ReadWriteStream & {
|
|
195
|
+
destroy: (err?: Error) => void
|
|
196
|
+
}
|
|
197
|
+
const originalDestroy = splitterWithDestroy.destroy.bind(splitterWithDestroy)
|
|
198
|
+
|
|
199
|
+
const startBulkHandler = () => {
|
|
200
|
+
if (isTransportClosed || isBulkHandlerActive) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
isBulkHandlerActive = true
|
|
204
|
+
initializeBulkHandler(opts, client, splitter, () => {
|
|
205
|
+
isBulkHandlerActive = false
|
|
206
|
+
scheduleBulkHandlerRestart()
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const scheduleBulkHandlerRestart = () => {
|
|
211
|
+
if (isTransportClosed || isRestartScheduled) {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
isRestartScheduled = true
|
|
215
|
+
|
|
216
|
+
if (typeof pool.resurrect === 'function') {
|
|
217
|
+
pool.resurrect({ name: 'elasticsearch-js' })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const retryDelayMs = Math.min(
|
|
221
|
+
Number(opts.flushInterval ?? opts['flush-interval'] ?? 3000),
|
|
222
|
+
5000
|
|
223
|
+
)
|
|
224
|
+
const timer = setTimeout(() => {
|
|
225
|
+
isRestartScheduled = false
|
|
226
|
+
startBulkHandler()
|
|
227
|
+
}, retryDelayMs)
|
|
228
|
+
timer.unref?.()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
splitterWithDestroy.destroy = function (err?: Error) {
|
|
232
|
+
if (err && !isTransportClosed) {
|
|
233
|
+
scheduleBulkHandlerRestart()
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
isTransportClosed = true
|
|
237
|
+
originalDestroy(err)
|
|
238
|
+
}
|
|
239
|
+
|
|
196
240
|
client.diagnostic.on('resurrect', () => {
|
|
197
|
-
|
|
241
|
+
if (!isBulkHandlerActive) {
|
|
242
|
+
scheduleBulkHandlerRestart()
|
|
243
|
+
}
|
|
198
244
|
})
|
|
199
245
|
|
|
200
|
-
|
|
246
|
+
startBulkHandler()
|
|
201
247
|
|
|
202
248
|
return splitter
|
|
203
249
|
}
|
package/src/lib/logger.ts
CHANGED
|
@@ -52,36 +52,6 @@ const isPlainObject = (v: unknown): v is Record<string, unknown> =>
|
|
|
52
52
|
!(v instanceof Error) &&
|
|
53
53
|
!(v instanceof Date)
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
* Reduces object values to scalars for top-level ES fields.
|
|
57
|
-
* ES text/keyword fields reject nested objects; use _id or truncated JSON.
|
|
58
|
-
*/
|
|
59
|
-
const toScalarForTopLevel = (
|
|
60
|
-
value: unknown
|
|
61
|
-
): string | number | boolean | null => {
|
|
62
|
-
if (value === null || value === undefined) return null
|
|
63
|
-
if (
|
|
64
|
-
typeof value === 'string' ||
|
|
65
|
-
typeof value === 'number' ||
|
|
66
|
-
typeof value === 'boolean'
|
|
67
|
-
)
|
|
68
|
-
return value
|
|
69
|
-
if (value instanceof Date) return value.toISOString()
|
|
70
|
-
if (isObjectIdLike(value)) return value.toHexString()
|
|
71
|
-
if (value instanceof Error) return value.message
|
|
72
|
-
if (isPlainObject(value) && '_id' in value) {
|
|
73
|
-
const id = (value as { _id?: unknown })._id
|
|
74
|
-
if (id != null) {
|
|
75
|
-
if (typeof id === 'string') return id
|
|
76
|
-
if (isObjectIdLike(id)) return id.toHexString()
|
|
77
|
-
if (typeof id === 'object' && id !== null && 'toString' in id)
|
|
78
|
-
return String((id as { toString: () => string }).toString())
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const str = typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
82
|
-
return str.length > 200 ? `${str.slice(0, 200)}...` : str
|
|
83
|
-
}
|
|
84
|
-
|
|
85
55
|
/**
|
|
86
56
|
* Recursively sanitize log values for Elasticsearch safety.
|
|
87
57
|
* - Remaps reserved key names (e.g. `_id` -> `mongo_id`)
|
|
@@ -491,19 +461,17 @@ class Logger {
|
|
|
491
461
|
ecs.trace_id = trace.traceId
|
|
492
462
|
}
|
|
493
463
|
|
|
494
|
-
// Structured context:
|
|
495
|
-
//
|
|
464
|
+
// Structured context: put in single 'context' field to avoid ES mapping conflicts.
|
|
465
|
+
// Flattening to top-level caused document_parsing_exception (object vs scalar type mismatches).
|
|
466
|
+
// Nesting in context keeps structure consistent and avoids per-field mapping conflicts.
|
|
467
|
+
let context: Record<string, unknown> | undefined
|
|
496
468
|
let detail: unknown
|
|
497
469
|
if (
|
|
498
470
|
args.length === 1 &&
|
|
499
471
|
isPlainObject(args[0]) &&
|
|
500
472
|
Object.keys(args[0]).length > 0
|
|
501
473
|
) {
|
|
502
|
-
|
|
503
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
504
|
-
const key = toSafeElasticFieldName(k)
|
|
505
|
-
ecs[key] = toScalarForTopLevel(v)
|
|
506
|
-
}
|
|
474
|
+
context = sanitizeForElastic(args[0]) as Record<string, unknown>
|
|
507
475
|
} else {
|
|
508
476
|
detail = isLocal ? args : JSON.stringify(sanitizeForElastic(args))
|
|
509
477
|
}
|
|
@@ -515,6 +483,9 @@ class Logger {
|
|
|
515
483
|
code: event.code,
|
|
516
484
|
msg: event.msg,
|
|
517
485
|
}
|
|
486
|
+
if (context !== undefined) {
|
|
487
|
+
base.context = context
|
|
488
|
+
}
|
|
518
489
|
if (detail !== undefined) {
|
|
519
490
|
base.detail = detail
|
|
520
491
|
}
|