@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.
@@ -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: indexName(),
59
+ refreshOnCompletion: false,
60
60
  onDocument(doc) {
61
61
  var _a, _b;
62
62
  const d = doc;
@@ -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
- // ECS-aligned fields for Kibana Discover, Lens, alerts
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
- 'log.level': logLevel,
265
- 'log.logger': this._name,
266
- 'event.code': logEvent.code,
267
- message: logEvent.msg,
268
- service: {
269
- name: (_a = process.env.SERVER_NICKNAME) !== null && _a !== void 0 ? _a : 'unknown',
270
- environment: (_b = process.env.NODE_ENV) !== null && _b !== void 0 ? _b : 'development',
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] = v;
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: logEvent.code,
301
- msg: logEvent.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmdhammoud/meritt-utils",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
@@ -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: indexName(),
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(logLevel: LOG_LEVEL, logEvent: LogEvent, args: unknown[]) {
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
- // ECS-aligned fields for Kibana Discover, Lens, alerts
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
- 'log.level': logLevel,
318
- 'log.logger': this._name,
319
- 'event.code': logEvent.code,
320
- message: logEvent.msg,
321
- service: {
322
- name: process.env.SERVER_NICKNAME ?? 'unknown',
323
- environment: process.env.NODE_ENV ?? 'development',
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
- ;(ecs as Record<string, unknown>).trace = {id: trace.traceId}
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] as Record<string, unknown>
358
+ const obj = args[0]
344
359
  for (const [k, v] of Object.entries(obj)) {
345
360
  const key = toSnakeCase(k)
346
- ;(ecs as Record<string, unknown>)[key] = v
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: logEvent.code,
357
- msg: logEvent.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
@@ -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
  /**