@mhmdhammoud/meritt-utils 1.5.7 → 1.5.9

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 flattened)', () => {
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 dynamically', () => {
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,11 +121,11 @@ describe('route and format logs', () => {
122
121
  _index: 'bad-index',
123
122
  },
124
123
  });
124
+ // Context holds sanitized structure; reserved names remapped recursively
125
125
  expect(PINO.info).toHaveBeenCalledWith(expect.objectContaining({
126
- mongo_id: 'abc123',
127
- nested: expect.objectContaining({
128
- mongo_id: 'nested-1',
129
- es_index: 'bad-index',
126
+ context: expect.objectContaining({
127
+ mongo_id: 'abc123',
128
+ nested: { mongo_id: 'nested-1', es_index: 'bad-index' },
130
129
  }),
131
130
  }));
132
131
  expect(PINO.info).not.toHaveBeenCalledWith(expect.objectContaining({
@@ -381,20 +381,18 @@ class Logger {
381
381
  if (trace) {
382
382
  ecs.trace_id = trace.traceId;
383
383
  }
384
- // Structured context: flatten single plain object as top-level fields
385
- // Sanitize values to avoid ES mapping conflicts (e.g. Error objects serializable shape)
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;
386
388
  let detail;
387
389
  if (args.length === 1 &&
388
390
  isPlainObject(args[0]) &&
389
391
  Object.keys(args[0]).length > 0) {
390
- const obj = args[0];
391
- for (const [k, v] of Object.entries(obj)) {
392
- const key = toSafeElasticFieldName(k);
393
- ecs[key] = sanitizeForElastic(v);
394
- }
392
+ context = sanitizeForElastic(args[0]);
395
393
  }
396
394
  else {
397
- detail = isLocal ? args : JSON.stringify(args);
395
+ detail = isLocal ? args : JSON.stringify(sanitizeForElastic(args));
398
396
  }
399
397
  // Legacy fields for backward compatibility (component, code, msg)
400
398
  const base = {
@@ -403,6 +401,9 @@ class Logger {
403
401
  code: event.code,
404
402
  msg: event.msg,
405
403
  };
404
+ if (context !== undefined) {
405
+ base.context = context;
406
+ }
406
407
  if (detail !== undefined) {
407
408
  base.detail = detail;
408
409
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmdhammoud/meritt-utils",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
@@ -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 flattened)', () => {
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 dynamically', () => {
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,12 +104,12 @@ describe('route and format logs', () => {
105
104
  },
106
105
  })
107
106
 
107
+ // Context holds sanitized structure; reserved names remapped recursively
108
108
  expect(PINO.info).toHaveBeenCalledWith(
109
109
  expect.objectContaining({
110
- mongo_id: 'abc123',
111
- nested: expect.objectContaining({
112
- mongo_id: 'nested-1',
113
- es_index: 'bad-index',
110
+ context: expect.objectContaining({
111
+ mongo_id: 'abc123',
112
+ nested: { mongo_id: 'nested-1', es_index: 'bad-index' },
114
113
  }),
115
114
  })
116
115
  )
package/src/lib/logger.ts CHANGED
@@ -461,21 +461,19 @@ class Logger {
461
461
  ecs.trace_id = trace.traceId
462
462
  }
463
463
 
464
- // Structured context: flatten single plain object as top-level fields
465
- // Sanitize values to avoid ES mapping conflicts (e.g. Error objects serializable shape)
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
466
468
  let detail: unknown
467
469
  if (
468
470
  args.length === 1 &&
469
471
  isPlainObject(args[0]) &&
470
472
  Object.keys(args[0]).length > 0
471
473
  ) {
472
- const obj = args[0]
473
- for (const [k, v] of Object.entries(obj)) {
474
- const key = toSafeElasticFieldName(k)
475
- ecs[key] = sanitizeForElastic(v)
476
- }
474
+ context = sanitizeForElastic(args[0]) as Record<string, unknown>
477
475
  } else {
478
- detail = isLocal ? args : JSON.stringify(args)
476
+ detail = isLocal ? args : JSON.stringify(sanitizeForElastic(args))
479
477
  }
480
478
 
481
479
  // Legacy fields for backward compatibility (component, code, msg)
@@ -485,6 +483,9 @@ class Logger {
485
483
  code: event.code,
486
484
  msg: event.msg,
487
485
  }
486
+ if (context !== undefined) {
487
+ base.context = context
488
+ }
488
489
  if (detail !== undefined) {
489
490
  base.detail = detail
490
491
  }