@mhmdhammoud/meritt-utils 1.5.5 → 1.5.6
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.
- package/dist/lib/elastic-transport.js +1 -1
- package/dist/lib/logger.js +27 -17
- package/package.json +1 -1
- package/src/lib/elastic-transport.ts +1 -1
- package/src/lib/logger.ts +45 -27
- package/src/lib/trace-store.ts +4 -4
|
@@ -56,7 +56,7 @@ function initializeBulkHandler(opts, client, splitter) {
|
|
|
56
56
|
datasource: splitter,
|
|
57
57
|
flushBytes: (_e = (_d = opts.flushBytes) !== null && _d !== void 0 ? _d : opts['flush-bytes']) !== null && _e !== void 0 ? _e : 1000,
|
|
58
58
|
flushInterval: (_g = (_f = opts.flushInterval) !== null && _f !== void 0 ? _f : opts['flush-interval']) !== null && _g !== void 0 ? _g : 3000,
|
|
59
|
-
refreshOnCompletion:
|
|
59
|
+
refreshOnCompletion: false,
|
|
60
60
|
onDocument(doc) {
|
|
61
61
|
var _a, _b;
|
|
62
62
|
const d = doc;
|
package/dist/lib/logger.js
CHANGED
|
@@ -208,6 +208,13 @@ function getLogger(elasticConfig) {
|
|
|
208
208
|
esTransport.on('insertError', (err) => {
|
|
209
209
|
console.error('[Logger] Elasticsearch insert error:', err.message);
|
|
210
210
|
console.error('[Logger] Some logs failed to index to Elasticsearch.');
|
|
211
|
+
if (err.document) {
|
|
212
|
+
const docStr = JSON.stringify(err.document);
|
|
213
|
+
const preview = docStr.length > 500
|
|
214
|
+
? `${docStr.substring(0, 500)}... (truncated)`
|
|
215
|
+
: docStr;
|
|
216
|
+
console.error('[Logger] Dropped document preview:', preview);
|
|
217
|
+
}
|
|
211
218
|
});
|
|
212
219
|
// Log successful connection (for debugging)
|
|
213
220
|
esTransport.on('insert', () => {
|
|
@@ -259,27 +266,27 @@ class Logger {
|
|
|
259
266
|
buildPayload(logLevel, logEvent, args) {
|
|
260
267
|
var _a, _b;
|
|
261
268
|
const isLocal = process.env.NODE_ENV === 'local' || process.env.NODE_ENV === 'test';
|
|
262
|
-
//
|
|
269
|
+
// Defensive: missing Logs constant (undefined) crashes on logEvent.code
|
|
270
|
+
const event = logEvent && typeof logEvent === 'object' && 'code' in logEvent
|
|
271
|
+
? logEvent
|
|
272
|
+
: { code: 'UNKNOWN', msg: 'Missing or invalid log event constant' };
|
|
273
|
+
// ECS-aligned fields for Kibana (flat names to avoid mapping conflicts with existing indices)
|
|
263
274
|
const ecs = {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
message:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
},
|
|
272
|
-
host: {
|
|
273
|
-
name: (0, os_1.hostname)(),
|
|
274
|
-
},
|
|
275
|
+
log_level: logLevel,
|
|
276
|
+
log_logger: this._name,
|
|
277
|
+
event_code: event.code,
|
|
278
|
+
message: event.msg,
|
|
279
|
+
service_name: (_a = process.env.SERVER_NICKNAME) !== null && _a !== void 0 ? _a : 'unknown',
|
|
280
|
+
service_environment: (_b = process.env.NODE_ENV) !== null && _b !== void 0 ? _b : 'development',
|
|
281
|
+
host_name: (0, os_1.hostname)(),
|
|
275
282
|
};
|
|
276
283
|
// Trace context for request-scoped correlation
|
|
277
284
|
const trace = (0, trace_store_1.getTraceContext)();
|
|
278
285
|
if (trace) {
|
|
279
|
-
;
|
|
280
|
-
ecs.trace = { id: trace.traceId };
|
|
286
|
+
ecs.trace_id = trace.traceId;
|
|
281
287
|
}
|
|
282
288
|
// Structured context: flatten single plain object as top-level fields
|
|
289
|
+
// Sanitize values to avoid ES mapping conflicts (e.g. Error objects → serializable shape)
|
|
283
290
|
let detail;
|
|
284
291
|
if (args.length === 1 &&
|
|
285
292
|
isPlainObject(args[0]) &&
|
|
@@ -287,7 +294,10 @@ class Logger {
|
|
|
287
294
|
const obj = args[0];
|
|
288
295
|
for (const [k, v] of Object.entries(obj)) {
|
|
289
296
|
const key = toSnakeCase(k);
|
|
290
|
-
ecs[key] =
|
|
297
|
+
ecs[key] =
|
|
298
|
+
v instanceof Error
|
|
299
|
+
? { message: v.message, type: v.constructor.name }
|
|
300
|
+
: v;
|
|
291
301
|
}
|
|
292
302
|
}
|
|
293
303
|
else {
|
|
@@ -297,8 +307,8 @@ class Logger {
|
|
|
297
307
|
const base = {
|
|
298
308
|
...ecs,
|
|
299
309
|
component: this._name,
|
|
300
|
-
code:
|
|
301
|
-
msg:
|
|
310
|
+
code: event.code,
|
|
311
|
+
msg: event.msg,
|
|
302
312
|
};
|
|
303
313
|
if (detail !== undefined) {
|
|
304
314
|
base.detail = detail;
|
package/package.json
CHANGED
|
@@ -107,7 +107,7 @@ function initializeBulkHandler(
|
|
|
107
107
|
datasource: splitter as unknown as Readable,
|
|
108
108
|
flushBytes: opts.flushBytes ?? opts['flush-bytes'] ?? 1000,
|
|
109
109
|
flushInterval: opts.flushInterval ?? opts['flush-interval'] ?? 3000,
|
|
110
|
-
refreshOnCompletion:
|
|
110
|
+
refreshOnCompletion: false,
|
|
111
111
|
onDocument(doc: unknown) {
|
|
112
112
|
const d = doc as LogDocument
|
|
113
113
|
const date = d.time ?? d['@timestamp'] ?? new Date().toISOString()
|
package/src/lib/logger.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {Logger as PinoLogger, pino, stdTimeFunctions} from 'pino'
|
|
1
|
+
import { Logger as PinoLogger, pino, stdTimeFunctions } from 'pino'
|
|
2
2
|
import * as dotenv from 'dotenv'
|
|
3
|
-
import {hostname} from 'os'
|
|
4
|
-
import {createElasticTransport} from './elastic-transport'
|
|
5
|
-
import {getTraceContext} from './trace-store'
|
|
6
|
-
import {LOG_LEVEL, LogEvent, ElasticConfig} from '../types'
|
|
3
|
+
import { hostname } from 'os'
|
|
4
|
+
import { createElasticTransport } from './elastic-transport'
|
|
5
|
+
import { getTraceContext } from './trace-store'
|
|
6
|
+
import { LOG_LEVEL, LogEvent, ElasticConfig } from '../types'
|
|
7
7
|
|
|
8
8
|
dotenv.config()
|
|
9
9
|
|
|
@@ -228,9 +228,17 @@ function getLogger(elasticConfig?: ElasticConfig): PinoLogger {
|
|
|
228
228
|
})
|
|
229
229
|
|
|
230
230
|
// Handle insert errors (document indexing failures)
|
|
231
|
-
esTransport.on('insertError', (err: Error) => {
|
|
231
|
+
esTransport.on('insertError', (err: Error & { document?: unknown }) => {
|
|
232
232
|
console.error('[Logger] Elasticsearch insert error:', err.message)
|
|
233
233
|
console.error('[Logger] Some logs failed to index to Elasticsearch.')
|
|
234
|
+
if (err.document) {
|
|
235
|
+
const docStr = JSON.stringify(err.document)
|
|
236
|
+
const preview =
|
|
237
|
+
docStr.length > 500
|
|
238
|
+
? `${docStr.substring(0, 500)}... (truncated)`
|
|
239
|
+
: docStr
|
|
240
|
+
console.error('[Logger] Dropped document preview:', preview)
|
|
241
|
+
}
|
|
234
242
|
})
|
|
235
243
|
|
|
236
244
|
// Log successful connection (for debugging)
|
|
@@ -308,42 +316,52 @@ class Logger {
|
|
|
308
316
|
* - Structured: Single plain object flattened as top-level snake_case fields (Kibana filterable)
|
|
309
317
|
* - Trace: trace.id when running inside runWithTrace
|
|
310
318
|
*/
|
|
311
|
-
private buildPayload(
|
|
319
|
+
private buildPayload(
|
|
320
|
+
logLevel: LOG_LEVEL,
|
|
321
|
+
logEvent: LogEvent,
|
|
322
|
+
args: unknown[]
|
|
323
|
+
) {
|
|
312
324
|
const isLocal =
|
|
313
325
|
process.env.NODE_ENV === 'local' || process.env.NODE_ENV === 'test'
|
|
314
326
|
|
|
315
|
-
//
|
|
327
|
+
// Defensive: missing Logs constant (undefined) crashes on logEvent.code
|
|
328
|
+
const event =
|
|
329
|
+
logEvent && typeof logEvent === 'object' && 'code' in logEvent
|
|
330
|
+
? logEvent
|
|
331
|
+
: { code: 'UNKNOWN', msg: 'Missing or invalid log event constant' }
|
|
332
|
+
|
|
333
|
+
// ECS-aligned fields for Kibana (flat names to avoid mapping conflicts with existing indices)
|
|
316
334
|
const ecs: Record<string, unknown> = {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
message:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
},
|
|
325
|
-
host: {
|
|
326
|
-
name: hostname(),
|
|
327
|
-
},
|
|
335
|
+
log_level: logLevel,
|
|
336
|
+
log_logger: this._name,
|
|
337
|
+
event_code: event.code,
|
|
338
|
+
message: event.msg,
|
|
339
|
+
service_name: process.env.SERVER_NICKNAME ?? 'unknown',
|
|
340
|
+
service_environment: process.env.NODE_ENV ?? 'development',
|
|
341
|
+
host_name: hostname(),
|
|
328
342
|
}
|
|
329
343
|
|
|
330
344
|
// Trace context for request-scoped correlation
|
|
331
345
|
const trace = getTraceContext()
|
|
332
346
|
if (trace) {
|
|
333
|
-
|
|
347
|
+
ecs.trace_id = trace.traceId
|
|
334
348
|
}
|
|
335
349
|
|
|
336
350
|
// Structured context: flatten single plain object as top-level fields
|
|
351
|
+
// Sanitize values to avoid ES mapping conflicts (e.g. Error objects → serializable shape)
|
|
337
352
|
let detail: unknown
|
|
338
353
|
if (
|
|
339
354
|
args.length === 1 &&
|
|
340
355
|
isPlainObject(args[0]) &&
|
|
341
356
|
Object.keys(args[0]).length > 0
|
|
342
357
|
) {
|
|
343
|
-
const obj = args[0]
|
|
358
|
+
const obj = args[0]
|
|
344
359
|
for (const [k, v] of Object.entries(obj)) {
|
|
345
360
|
const key = toSnakeCase(k)
|
|
346
|
-
|
|
361
|
+
ecs[key] =
|
|
362
|
+
v instanceof Error
|
|
363
|
+
? { message: v.message, type: v.constructor.name }
|
|
364
|
+
: v
|
|
347
365
|
}
|
|
348
366
|
} else {
|
|
349
367
|
detail = isLocal ? args : JSON.stringify(args)
|
|
@@ -353,8 +371,8 @@ class Logger {
|
|
|
353
371
|
const base: Record<string, unknown> = {
|
|
354
372
|
...ecs,
|
|
355
373
|
component: this._name,
|
|
356
|
-
code:
|
|
357
|
-
msg:
|
|
374
|
+
code: event.code,
|
|
375
|
+
msg: event.msg,
|
|
358
376
|
}
|
|
359
377
|
if (detail !== undefined) {
|
|
360
378
|
base.detail = detail
|
|
@@ -431,7 +449,7 @@ class Logger {
|
|
|
431
449
|
const result = await fn()
|
|
432
450
|
const durationMs = Date.now() - start
|
|
433
451
|
const payload = this.buildPayload('info', logEvent, [
|
|
434
|
-
{...context, duration_ms: durationMs, success: true},
|
|
452
|
+
{ ...context, duration_ms: durationMs, success: true },
|
|
435
453
|
])
|
|
436
454
|
this._logger.info(payload)
|
|
437
455
|
return result
|
|
@@ -439,10 +457,10 @@ class Logger {
|
|
|
439
457
|
const durationMs = Date.now() - start
|
|
440
458
|
const errObj =
|
|
441
459
|
error instanceof Error
|
|
442
|
-
? {error_message: error.message, error_type: error.constructor.name}
|
|
460
|
+
? { error_message: error.message, error_type: error.constructor.name }
|
|
443
461
|
: {}
|
|
444
462
|
const payload = this.buildPayload('error', logEvent, [
|
|
445
|
-
{...context, ...errObj, duration_ms: durationMs, success: false},
|
|
463
|
+
{ ...context, ...errObj, duration_ms: durationMs, success: false },
|
|
446
464
|
])
|
|
447
465
|
this._logger.error(payload)
|
|
448
466
|
throw error
|
package/src/lib/trace-store.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {AsyncLocalStorage} from 'async_hooks'
|
|
2
|
-
import {randomUUID} from 'crypto'
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks'
|
|
2
|
+
import { randomUUID } from 'crypto'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Trace context for request-scoped correlation in Kibana.
|
|
@@ -27,7 +27,7 @@ export const runWithTrace = async <T>(
|
|
|
27
27
|
traceId?: string
|
|
28
28
|
): Promise<T> => {
|
|
29
29
|
const id = traceId ?? randomUUID()
|
|
30
|
-
return traceStorage.run({traceId: id}, () => fn())
|
|
30
|
+
return traceStorage.run({ traceId: id }, () => fn())
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -35,7 +35,7 @@ export const runWithTrace = async <T>(
|
|
|
35
35
|
*/
|
|
36
36
|
export const runWithTraceSync = <T>(fn: () => T, traceId?: string): T => {
|
|
37
37
|
const id = traceId ?? randomUUID()
|
|
38
|
-
return traceStorage.run({traceId: id}, () => fn())
|
|
38
|
+
return traceStorage.run({ traceId: id }, () => fn())
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|