@hemia/trace-manager 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1,116 +1,517 @@
1
1
  # @hemia/trace-manager
2
2
 
3
- Sistema de gestión de trazabilidad para aplicaciones Node.js. Permite registrar eventos, errores y transacciones utilizando un repositorio NoSQL, con soporte para decoradores automáticos de traza (`@Traceable`) y segmentación por `traceId`, `checkpoint`, `functionCode` y `tags`.
3
+ Sistema de trazabilidad distribuida basado en **OpenTelemetry** para aplicaciones Node.js. Proporciona decoradores automáticos, logging contextual correlacionado con trazas y compatibilidad con ClickHouse para análisis de observabilidad.
4
4
 
5
5
  ---
6
6
 
7
- ## Instalación
7
+ ## 🚀 Características
8
+
9
+ - ✅ **Decorador `@Trace`** - Instrumentación automática de métodos con captura de inputs/outputs
10
+ - ✅ **AsyncLocalStorage** - Propagación automática de contexto sin pasar parámetros manualmente
11
+ - ✅ **Logger Contextual** - Logs automáticamente correlacionados con `traceId` y `spanId`
12
+ - ✅ **Formato OpenTelemetry** - Compatible con estándares de observabilidad
13
+ - ✅ **ClickHouse Ready** - Esquema optimizado para análisis en ClickHouse
14
+ - ✅ **Captura de Errores** - Excepciones registradas como eventos en spans
15
+ - ✅ **Jerarquía de Spans** - Parent-Child spans automáticos por nivel de invocación
16
+
17
+ ---
18
+
19
+ ## 📦 Instalación
8
20
 
9
21
  ```bash
10
- npm install @hemia/trace-manager
22
+ npm install @hemia/trace-manager @hemia/app-context
11
23
  ```
12
24
 
13
25
  ---
14
26
 
15
- ## Componentes principales
27
+ ## 🎯 Componentes principales
28
+
29
+ ### ✅ Decorador `@Trace()`
30
+
31
+ Instrumenta automáticamente métodos creando spans de OpenTelemetry con captura de inputs, outputs y errores.
32
+
33
+ #### **Características:**
16
34
 
17
- ### `TraceManager<T>`
35
+ - 🔹 Crea span automáticamente con `SpanId` único
36
+ - 🔹 Captura **metadata** de argumentos (tipos, cantidad) - no valores completos
37
+ - 🔹 Captura **metadata** de resultados (tipo, isArray, length) - no valores completos
38
+ - 🔹 Registra excepciones como eventos (`EventsNested`)
39
+ - 🔹 Calcula duración en nanosegundos (`DurationUInt64`)
40
+ - 🔹 Propaga contexto automáticamente a métodos hijos
41
+ - 🔹 **Privacy-first**: No serializa valores sensibles, solo metadata
18
42
 
19
- Clase encargada de registrar y consultar trazas desde un repositorio MongoDB usando `NoSQLRepository`.
43
+ #### **Uso básico:**
44
+
45
+ ```ts
46
+ import { Trace } from '@hemia/trace-manager';
47
+
48
+ class UserService {
49
+ @Trace()
50
+ async createUser(userData: any) {
51
+ // El decorador captura automáticamente:
52
+ // - Metadata de input: tipo, cantidad de args (NO valores completos)
53
+ // - Metadata de output: tipo, isArray, length (NO valores completos)
54
+ // - Duración de ejecución en nanosegundos
55
+ // - Excepciones si ocurren (con stacktrace)
56
+
57
+ const user = await this.repository.save(userData);
58
+ return user;
59
+ }
60
+
61
+ @Trace({ name: 'validate-user-email' })
62
+ private async validateEmail(email: string) {
63
+ // Span hijo automático (hereda ParentSpanId)
64
+ return await this.emailValidator.check(email);
65
+ }
66
+ }
67
+ ```
20
68
 
21
- #### Métodos disponibles:
69
+ #### **Opciones de configuración:**
22
70
 
23
- * `save(traceId, additionalData?)`: Guarda una traza
24
- * `find(filter, options?)`: Busca múltiples trazas
25
- * `findOne(filter)`: Busca una traza específica
26
- * `getById(id)`: Obtiene una traza por ID
27
- * `update(filter, data)`: Actualiza una traza
28
- * `delete(filter)`: Elimina una traza
29
- * `aggregate(pipeline)`: Ejecuta una agregación personalizada
71
+ ```ts
72
+ interface TraceOptions {
73
+ name?: string; // Nombre personalizado del span (default: ClassName.methodName)
74
+ kind?: 'SPAN_KIND_INTERNAL' // Tipo de span: INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER
75
+ | 'SPAN_KIND_SERVER'
76
+ | 'SPAN_KIND_CLIENT'
77
+ | 'SPAN_KIND_PRODUCER'
78
+ | 'SPAN_KIND_CONSUMER';
79
+ attributes?: Record<string, string>; // Atributos personalizados
80
+ }
81
+ ```
30
82
 
31
- #### Ejemplo:
83
+ #### **Ejemplo avanzado:**
32
84
 
33
85
  ```ts
34
- const manager = new TraceManager<TraceDocument>(traceRepo);
35
- await manager.save('trace-id-123', { message: 'Evento recibido' });
86
+ class PaymentService {
87
+ @Trace({
88
+ name: 'process-payment-stripe',
89
+ kind: 'SPAN_KIND_CLIENT',
90
+ attributes: { 'payment.provider': 'stripe' }
91
+ })
92
+ async processPayment(amount: number, currency: string) {
93
+ // Span con atributos personalizados
94
+ return await this.stripeClient.charge(amount, currency);
95
+ }
96
+ }
36
97
  ```
37
98
 
38
99
  ---
39
100
 
40
- ### ✅ Decorador `@Traceable()`
101
+ ### ✅ Logger Contextual
41
102
 
42
- Permite trazar automáticamente métodos con input/output y errores.
103
+ Logger que automáticamente correlaciona logs con el `traceId` y `spanId` activo, permitiendo rastrear logs específicos dentro de una traza distribuida.
43
104
 
44
- #### Uso:
105
+ #### **Características:**
45
106
 
46
- ```ts
47
- import { Traceable } from '@hemia/trace-manager';
107
+ - 🔹 Correlación automática con `TraceId` y `SpanId`
108
+ - 🔹 Niveles de severidad: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`
109
+ - 🔹 Atributos personalizados en cada log
110
+ - 🔹 Compatible con formato OpenTelemetry para ClickHouse
48
111
 
49
- class MiServicio {
50
- @Traceable({ ck: 'MiServicio', fn: 'procesar' })
51
- async procesar(data: any) {
52
- // lógica
53
- return { ok: true };
112
+ #### **Uso:**
113
+
114
+ ```ts
115
+ import { logger } from '@hemia/trace-manager';
116
+
117
+ class OrderService {
118
+ @Trace()
119
+ async placeOrder(order: Order) {
120
+ logger.info('Processing order', { orderId: order.id, amount: order.total });
121
+
122
+ try {
123
+ const result = await this.payment.charge(order.total);
124
+ logger.info('Payment successful', { transactionId: result.id });
125
+ return result;
126
+ } catch (error) {
127
+ logger.error('Payment failed', { error: error.message });
128
+ throw error;
129
+ }
54
130
  }
55
131
  }
56
132
  ```
57
133
 
58
- * Input se traza al inicio
59
- * Output se traza al finalizar
60
- * Error se traza si ocurre una excepción
134
+ #### **Métodos disponibles:**
135
+
136
+ ```ts
137
+ logger.debug(message: string, attributes?: Record<string, any>): void
138
+ logger.info(message: string, attributes?: Record<string, any>): void
139
+ logger.warn(message: string, attributes?: Record<string, any>): void
140
+ logger.error(message: string, attributes?: Record<string, any>): void
141
+ logger.fatal(message: string, attributes?: Record<string, any>): void
142
+ ```
61
143
 
62
144
  ---
63
145
 
64
- ### `addTrace()` y `endTrace()`
146
+ ## 🏗️ Estructura de Datos
147
+
148
+ ### **TraceSpan** (Span de OpenTelemetry)
65
149
 
66
- Funciones internas para agregar nodos de traza al contexto.
150
+ Mapea directamente con la tabla `otel_traces` de ClickHouse:
67
151
 
68
152
  ```ts
69
- const spanId = addTrace('Login', 'authUser', 'input', { email }, 'Inicio de login');
70
- // lógica
71
- endTrace(spanId, true, { userId });
153
+ interface TraceSpan {
154
+ Timestamp: string; // ISO 8601 - DateTime64(9)
155
+ TraceId: string; // ID único de la traza
156
+ SpanId: string; // ID único del span
157
+ ParentSpanId: string; // ID del span padre (jerarquía)
158
+ TraceState: string; // Estado de propagación W3C
159
+ ServiceName: string; // Nombre del servicio
160
+ SpanName: string; // Ej: "UserService.createUser"
161
+ SpanKind: 'SPAN_KIND_INTERNAL' | 'SPAN_KIND_SERVER' | 'SPAN_KIND_CLIENT' | ...;
162
+ DurationUInt64: bigint; // Duración en nanosegundos
163
+ StatusCode: 'STATUS_CODE_OK' | 'STATUS_CODE_ERROR' | 'STATUS_CODE_UNSET';
164
+ StatusMessage: string;
165
+ SpanAttributes: Record<string, string>; // app.method.args, app.method.result, etc.
166
+ ResourceAttributes: Record<string, string>; // host.name, service.name, etc.
167
+ EventsNested: SpanEvent[]; // Excepciones, logs puntuales
168
+ LinksNested: SpanLink[]; // Enlaces a otras trazas
169
+ }
170
+ ```
171
+
172
+ ### **TraceLog** (Log de OpenTelemetry)
173
+
174
+ Mapea directamente con la tabla `otel_logs` de ClickHouse:
175
+
176
+ ```ts
177
+ interface TraceLog {
178
+ Timestamp: string; // ISO 8601
179
+ TraceId: string; // Correlación automática con span activo
180
+ SpanId: string; // Span donde ocurrió el log
181
+ TraceFlags: number; // 1 = sampled
182
+ SeverityText: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
183
+ SeverityNumber: number; // 5, 9, 13, 17, 21
184
+ ServiceName: string;
185
+ Body: string; // Mensaje del log
186
+ LogAttributes: Record<string, string>; // Atributos custom
187
+ ResourceAttributes: Record<string, string>; // Metadata del servicio
188
+ }
72
189
  ```
73
190
 
74
191
  ---
75
192
 
76
- ## Tipos esperados
193
+ ## 🔗 Integración con `@hemia/app-context`
194
+
195
+ Este paquete utiliza `AsyncLocalStorage` a través de `@hemia/app-context` para mantener el contexto de trazabilidad sin necesidad de pasarlo explícitamente como parámetro.
196
+
197
+ ### **Requisitos:**
198
+
199
+ El contexto debe ser inicializado previamente usando `@hemia/app-context` (generalmente en un middleware HTTP):
77
200
 
78
201
  ```ts
79
- interface TraceNode {
80
- traceId: string;
81
- spanId: string;
82
- parentSpanId?: string;
83
- projectId: string;
84
- function: string;
85
- checkpoint: string;
86
- description?: string;
87
- inputData?: any;
88
- outputData?: any;
89
- error?: any;
90
- type: 'input' | 'output';
91
- status: boolean;
92
- tags?: string[];
93
- startTime: number;
94
- endTime: number;
95
- duration?: number;
96
- source?: string;
202
+ import { asyncContext } from '@hemia/app-context';
203
+
204
+ // En tu middleware o inicialización
205
+ const context = {
206
+ traceId: generateTraceId(),
207
+ spanId: generateSpanId(),
208
+ serviceName: 'user-service',
209
+ traceContext: {
210
+ resourceAttributes: {
211
+ 'service.name': 'user-service',
212
+ 'host.name': os.hostname(),
213
+ },
214
+ spans: [],
215
+ logs: []
216
+ }
217
+ };
218
+
219
+ await asyncContext.run(context, async () => {
220
+ // Todo el código dentro hereda este contexto automáticamente
221
+ await handleRequest(req, res);
222
+ });
223
+ ```
224
+
225
+ ---
226
+
227
+ ## 📊 Esquema ClickHouse
228
+
229
+ ### Tabla: `otel_traces`
230
+
231
+ ```sql
232
+ CREATE TABLE otel_traces (
233
+ Timestamp DateTime64(9) CODEC(Delta, ZSTD(1)),
234
+ TraceId String CODEC(ZSTD(1)),
235
+ SpanId String CODEC(ZSTD(1)),
236
+ ParentSpanId String CODEC(ZSTD(1)),
237
+ ServiceName LowCardinality(String) CODEC(ZSTD(1)),
238
+ SpanName LowCardinality(String) CODEC(ZSTD(1)),
239
+ SpanKind LowCardinality(String) CODEC(ZSTD(1)),
240
+ DurationUInt64 UInt64 CODEC(ZSTD(1)),
241
+ StatusCode LowCardinality(String) CODEC(ZSTD(1)),
242
+ SpanAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
243
+ ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
244
+ EventsNested Nested (
245
+ Timestamp DateTime64(9),
246
+ Name LowCardinality(String),
247
+ Attributes Map(LowCardinality(String), String)
248
+ ) CODEC(ZSTD(1))
249
+ ) ENGINE = MergeTree()
250
+ PARTITION BY toDate(Timestamp)
251
+ ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp), TraceId);
252
+ ```
253
+
254
+ ### Tabla: `otel_logs`
255
+
256
+ ```sql
257
+ CREATE TABLE otel_logs (
258
+ Timestamp DateTime64(9) CODEC(Delta, ZSTD(1)),
259
+ TraceId String CODEC(ZSTD(1)),
260
+ SpanId String CODEC(ZSTD(1)),
261
+ SeverityText LowCardinality(String) CODEC(ZSTD(1)),
262
+ SeverityNumber Int32 CODEC(ZSTD(1)),
263
+ ServiceName LowCardinality(String) CODEC(ZSTD(1)),
264
+ Body String CODEC(ZSTD(1)),
265
+ LogAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
266
+ ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1))
267
+ ) ENGINE = MergeTree()
268
+ PARTITION BY toDate(Timestamp)
269
+ ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId);
270
+ ```
271
+
272
+ ---
273
+
274
+ ## 🔍 Ejemplo completo
275
+
276
+ ```ts
277
+ import { Trace, logger } from '@hemia/trace-manager';
278
+ import { asyncContext } from '@hemia/app-context';
279
+
280
+ class OrderController {
281
+ @Trace({ name: 'http-create-order', kind: 'SPAN_KIND_SERVER' })
282
+ async createOrder(req: Request) {
283
+ logger.info('Order request received', { userId: req.userId });
284
+
285
+ const order = await this.orderService.create(req.body);
286
+
287
+ logger.info('Order created successfully', { orderId: order.id });
288
+ return order;
289
+ }
290
+ }
291
+
292
+ class OrderService {
293
+ @Trace()
294
+ async create(orderData: any) {
295
+ // Span hijo automático (ParentSpanId = span del controller)
296
+ logger.debug('Validating order data');
297
+
298
+ await this.validate(orderData);
299
+ const order = await this.repository.save(orderData);
300
+
301
+ logger.info('Order persisted', { orderId: order.id });
302
+ return order;
303
+ }
304
+
305
+ @Trace({ name: 'validate-order-rules' })
306
+ private async validate(data: any) {
307
+ // Span nieto (hijo del método create)
308
+ if (!data.items?.length) {
309
+ logger.error('Validation failed: no items');
310
+ throw new Error('Order must have items');
311
+ }
312
+ }
97
313
  }
98
314
  ```
99
315
 
316
+ **Resultado en ClickHouse:**
317
+
318
+ 3 spans jerárquicos:
319
+ 1. `http-create-order` (root, SPAN_KIND_SERVER)
320
+ 2. `OrderService.create` (child, SPAN_KIND_INTERNAL)
321
+ 3. `validate-order-rules` (grandchild, SPAN_KIND_INTERNAL)
322
+
323
+ Y múltiples logs correlacionados con el mismo `TraceId`.
324
+
100
325
  ---
101
326
 
102
- ## Requisitos
327
+ ## 📝 Buenas Prácticas
103
328
 
104
- * `@hemia/app-context` para contexto y persistencia
329
+ 1. **Usar `@Trace()` en capas de negocio críticas**: Controllers, Services, Repositories
330
+ 2. **Logger en puntos de decisión**: Validaciones, llamadas externas, errores
331
+ 3. **Atributos significativos**: Agregar IDs de entidades, estados, providers
332
+ 4. **Nombres descriptivos**: Usar `name` en `@Trace()` para operaciones complejas
333
+ 5. **Metadata approach**: El decorador captura metadata (tipos, cantidad) en lugar de valores completos
334
+ 6. **Privacy by default**: No se serializan valores sensibles como passwords o tokens
105
335
 
106
336
  ---
107
337
 
108
- ## Licencia
338
+ ## 🛠️ Utilidades
339
+
340
+ ### Helpers para SpanAttributes (Metadata Approach)
341
+
342
+ El paquete incluye utilidades para agregar metadata en lugar de serializar datos completos, siguiendo las mejores prácticas de observabilidad:
343
+
344
+ #### **`addArgsMetadata(attributes, args)`**
345
+ Agrega metadata de argumentos de función:
346
+ ```ts
347
+ import { addArgsMetadata } from '@hemia/trace-manager';
348
+
349
+ const args = ['user@test.com', 123, { name: 'John' }];
350
+ addArgsMetadata(span.SpanAttributes, args);
351
+
352
+ // Resultado:
353
+ // {
354
+ // "app.method.args.count": "3",
355
+ // "app.method.args.types": "string,number,object"
356
+ // }
357
+ ```
358
+
359
+ #### **`addResultMetadata(attributes, result)`**
360
+ Agrega metadata de resultado de función:
361
+ ```ts
362
+ import { addResultMetadata } from '@hemia/trace-manager';
363
+
364
+ const result = [{ id: 1 }, { id: 2 }];
365
+ addResultMetadata(span.SpanAttributes, result);
366
+
367
+ // Resultado:
368
+ // {
369
+ // "app.method.result.type": "object",
370
+ // "app.method.result.isArray": "true",
371
+ // "app.method.result.length": "2"
372
+ // }
373
+ ```
374
+
375
+ #### **`addRequestBodyMetadata(attributes, body, prefix?)`**
376
+ Agrega metadata de Request body (para middlewares HTTP):
377
+ ```ts
378
+ import { addRequestBodyMetadata } from '@hemia/trace-manager';
379
+
380
+ const reqBody = { email: 'user@test.com', password: '***' };
381
+ addRequestBodyMetadata(span.SpanAttributes, reqBody);
382
+
383
+ // Resultado:
384
+ // {
385
+ // "http.request.body.exists": "true",
386
+ // "http.request.body.type": "object",
387
+ // "http.request.body.keys": "email,password",
388
+ // "http.request.body.keyCount": "2"
389
+ // }
390
+ ```
391
+
392
+ #### **`addResponseBodyMetadata(attributes, body, isError, prefix?)`**
393
+ Agrega metadata de Response body con captura inteligente de errores:
394
+ ```ts
395
+ import { addResponseBodyMetadata } from '@hemia/trace-manager';
396
+
397
+ const errorBody = {
398
+ code: 'AUTH_ERROR',
399
+ message: 'Invalid credentials'
400
+ };
401
+
402
+ addResponseBodyMetadata(
403
+ span.SpanAttributes,
404
+ errorBody,
405
+ true // isError = true activa captura de mensaje
406
+ );
407
+
408
+ // Resultado:
409
+ // {
410
+ // "http.response.body.exists": "true",
411
+ // "http.response.body.type": "object",
412
+ // "http.response.body.keys": "code,message",
413
+ // "http.response.body.keyCount": "2",
414
+ // "http.response.error.code": "AUTH_ERROR",
415
+ // "http.response.error.message": "Invalid credentials"
416
+ // }
417
+ ```
418
+
419
+ #### **`addObjectMetadata(attributes, data, prefix)`**
420
+ Agrega metadata genérica de cualquier objeto:
421
+ ```ts
422
+ import { addObjectMetadata } from '@hemia/trace-manager';
423
+
424
+ const payload = { items: [1, 2, 3], total: 100 };
425
+ addObjectMetadata(span.SpanAttributes, payload, 'order.payload');
426
+
427
+ // Resultado:
428
+ // {
429
+ // "order.payload.exists": "true",
430
+ // "order.payload.type": "object",
431
+ // "order.payload.keys": "items,total",
432
+ // "order.payload.keyCount": "2"
433
+ // }
434
+ ```
435
+
436
+ ### Uso en Middleware Custom
437
+
438
+ ```ts
439
+ import {
440
+ addRequestBodyMetadata,
441
+ addResponseBodyMetadata
442
+ } from '@hemia/trace-manager';
443
+
444
+ export const customMiddleware = () => {
445
+ return (req: Request, res: Response, next: NextFunction) => {
446
+ // ... inicializar span ...
447
+
448
+ res.on('finish', () => {
449
+ const isError = res.statusCode >= 400;
450
+
451
+ if (isError) {
452
+ // Capturar metadata en lugar de cuerpos completos
453
+ addRequestBodyMetadata(
454
+ span.SpanAttributes,
455
+ req.body
456
+ );
457
+
458
+ addResponseBodyMetadata(
459
+ span.SpanAttributes,
460
+ res.locals.responseBody,
461
+ isError // Captura mensaje de error si existe
462
+ );
463
+ }
464
+ });
465
+
466
+ next();
467
+ };
468
+ };
469
+ ```
470
+
471
+ ### Ventajas del Metadata Approach
472
+
473
+ ✅ **Menor storage**: 10-20 bytes vs potencialmente KB de JSON
474
+ ✅ **Privacy by default**: No serializa valores sensibles (passwords, tokens)
475
+ ✅ **Mejor cardinality**: Más eficiente para queries en ClickHouse
476
+ ✅ **Suficiente para debugging**: Keys y tipos son suficientes para diagnosticar
477
+ ✅ **Consistencia**: Mismo approach en decoradores y middlewares
478
+
479
+ ### `sanitizeArgs()` y `toSafeJSON()`
480
+
481
+ Utilidades legacy que previenen:
482
+ - Serialización de objetos circulares
483
+ - Filtrado de Request/Response de Express
484
+ - Overflow de tamaño en ClickHouse
485
+
486
+ **⚠️ Nota**: El decorador `@Trace` ahora usa metadata en lugar de estas utilidades, pero siguen disponibles para compatibilidad.
487
+
488
+ ---
489
+
490
+ ## 📚 Dependencias
491
+
492
+ - `@hemia/app-context` (^0.0.6) - Manejo de contexto con AsyncLocalStorage
493
+ - `uuid` (^10.0.0) - Generación de IDs únicos para spans
494
+
495
+ ## 🔄 Versión Actual
496
+
497
+ **v0.0.3** - Metadata approach implementado (Noviembre 2025)
498
+
499
+ ---
500
+
501
+ ## 📄 Licencia
109
502
 
110
503
  MIT
111
504
 
112
505
  ---
113
506
 
114
- ## Autor
507
+ ## 👨‍💻 Autor
508
+
509
+ **Hemia Technologies**
510
+
511
+ ---
512
+
513
+ ## 🔗 Recursos
115
514
 
116
- Hemia Technologies
515
+ - [OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel/)
516
+ - [ClickHouse Documentation](https://clickhouse.com/docs)
517
+ - [AsyncLocalStorage Node.js](https://nodejs.org/api/async_context.html)
@@ -1 +1 @@
1
- import{asyncContext as t}from"@hemia/app-context";var e;!function(t){t.TRACE="x-trace-id",t.CORRELATION="x-correlation-id"}(e||(e={}));const n={DEBUG:5,INFO:9,WARN:13,ERROR:17,FATAL:21};const r=new class{constructor(t){this.defaultServiceName=t}log(e,r,o){const s=t.getStore();if(!s)return console.warn("[Logger] No active context. Log not traced."),void console.log(`[${e}] ${r}`,o);const a={Timestamp:(new Date).toISOString(),TraceId:s.traceId,SpanId:s.spanId,TraceFlags:1,SeverityText:e,SeverityNumber:n[e],ServiceName:s.serviceName,Body:r,LogAttributes:this.stringifyAttributes(o||{}),ResourceAttributes:s.traceContext.resourceAttributes};s.traceContext.logs.push(a),"production"!==process.env.NODE_ENV&&console.log(`[${e}] [TraceId:${s.traceId.substring(0,8)}...] ${r}`,o||"")}stringifyAttributes(t){const e={};for(const[n,r]of Object.entries(t))if(null==r)e[n]="";else if("string"==typeof r)e[n]=r;else try{e[n]=JSON.stringify(r)}catch{e[n]=String(r)}return e}debug(t,e){this.log("DEBUG",t,e)}info(t,e){this.log("INFO",t,e)}warn(t,e){this.log("WARN",t,e)}error(t,e){this.log("ERROR",t,e)}fatal(t,e){this.log("FATAL",t,e)}};function o(t){try{const e=JSON.stringify(t,function(){const t=new WeakSet;return(e,n)=>{if(null===n)return null;if("object"==typeof n){if(t.has(n))return"[Circular]";t.add(n)}return"function"==typeof n?n.toString():n}}());return JSON.parse(e)}catch(e){return String(t)}}const s=["content-type","host","x-no-cookies","x-api-key","Authorization","origin"];function a(t){return t.map(t=>{if(function(t){return null!=t&&"object"==typeof t&&"string"==typeof t.method&&"string"==typeof t.url&&"object"==typeof t.headers}(t)){const e=s.reduce((e,n)=>{const r=n.toLowerCase();return t.headers[r]&&(e[r]=t.headers[r]),e},{});return{method:t.method,url:t.url,params:t.params,query:t.query,body:t.body,headers:e}}return function(t){return null!=t&&"object"==typeof t&&"number"==typeof t.statusCode&&"function"==typeof t.setHeader&&"function"==typeof t.end}(t)?"[ExpressResponseObject]":"function"==typeof t?"[Function next]":t})}for(var i,c=[],u=0;u<256;++u)c.push((u+256).toString(16).slice(1));var p=new Uint8Array(16);function d(){if(!i&&!(i="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return i(p)}var f={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function g(t,e,n){if(f.randomUUID&&!t)return f.randomUUID();var r=(t=t||{}).random||(t.rng||d)();return r[6]=15&r[6]|64,r[8]=63&r[8]|128,function(t,e=0){return(c[t[e+0]]+c[t[e+1]]+c[t[e+2]]+c[t[e+3]]+"-"+c[t[e+4]]+c[t[e+5]]+"-"+c[t[e+6]]+c[t[e+7]]+"-"+c[t[e+8]]+c[t[e+9]]+"-"+c[t[e+10]]+c[t[e+11]]+c[t[e+12]]+c[t[e+13]]+c[t[e+14]]+c[t[e+15]]).toLowerCase()}(r)}function l(e){return function(n,r,s){const i=s?.value;if("function"!=typeof i)throw new Error("@Trace can only be applied to methods");s.value=async function(...s){const c=t.getStore();if(!c)return console.warn("[Trace] No active context. Skipping trace."),await i.apply(this,s);const u=g(),p=this?.constructor?.name||n.name||"UnknownClass",d=e?.name||`${p}.${r}`,f=process.hrtime.bigint(),l=(new Date).toISOString(),m=c.spanId,y={Timestamp:l,TraceId:c.traceId,SpanId:u,ParentSpanId:m,TraceState:"",ServiceName:c.serviceName,SpanName:d,SpanKind:e?.kind||"SPAN_KIND_INTERNAL",DurationUInt64:BigInt(0),StatusCode:"STATUS_CODE_UNSET",StatusMessage:"",SpanAttributes:{"code.function":r,"code.namespace":p,...e?.attributes},ResourceAttributes:c.traceContext.resourceAttributes,EventsNested:[],LinksNested:[]},S=c.spanId;c.spanId=u;try{const t=a(s);y.SpanAttributes["app.method.args"]=o(t),y.SpanAttributes["app.method.args.count"]=s.length.toString();const e=await i.apply(this,s),n=o(a(Array.isArray(e)?e:[e]));return y.SpanAttributes["app.method.result"]=n.substring(0,2e3),y.StatusCode="STATUS_CODE_OK",e}catch(t){const e={Timestamp:(new Date).toISOString(),Name:"exception",Attributes:{"exception.type":t.constructor?.name||"Error","exception.message":t.message||"Unknown error","exception.stacktrace":(t.stack||"").substring(0,5e3)}};throw y.EventsNested.push(e),y.StatusCode="STATUS_CODE_ERROR",y.StatusMessage=t.message||"Unhandled exception",t}finally{const t=process.hrtime.bigint();y.DurationUInt64=t-f,c.traceContext.spans.push(y),c.spanId=S}}}}export{l as Trace,e as TraceHeader,r as logger};
1
+ import{asyncContext as t}from"@hemia/app-context";var e;!function(t){t.TRACE="x-trace-id",t.CORRELATION="x-correlation-id"}(e||(e={}));const r={DEBUG:5,INFO:9,WARN:13,ERROR:17,FATAL:21};const n=new class{constructor(t){this.defaultServiceName=t}log(e,n,o){const s=t.getStore();if(!s)return console.warn("[Logger] No active context. Log not traced."),void console.log(`[${e}] ${n}`,o);const i={Timestamp:(new Date).toISOString(),TraceId:s.traceId,SpanId:s.spanId,TraceFlags:1,SeverityText:e,SeverityNumber:r[e],ServiceName:s.serviceName,Body:n,LogAttributes:this.stringifyAttributes(o||{}),ResourceAttributes:s.traceContext.resourceAttributes};s.traceContext.logs.push(i),"production"!==process.env.NODE_ENV&&console.log(`[${e}] [TraceId:${s.traceId.substring(0,8)}...] ${n}`,o||"")}stringifyAttributes(t){const e={};for(const[r,n]of Object.entries(t))if(null==n)e[r]="";else if("string"==typeof n)e[r]=n;else try{e[r]=JSON.stringify(n)}catch{e[r]=String(n)}return e}debug(t,e){this.log("DEBUG",t,e)}info(t,e){this.log("INFO",t,e)}warn(t,e){this.log("WARN",t,e)}error(t,e){this.log("ERROR",t,e)}fatal(t,e){this.log("FATAL",t,e)}};for(var o,s=[],i=0;i<256;++i)s.push((i+256).toString(16).slice(1));var a=new Uint8Array(16);function c(){if(!o&&!(o="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return o(a)}var p={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function u(t,e,r){if(p.randomUUID&&!t)return p.randomUUID();var n=(t=t||{}).random||(t.rng||c)();return n[6]=15&n[6]|64,n[8]=63&n[8]|128,function(t,e=0){return(s[t[e+0]]+s[t[e+1]]+s[t[e+2]]+s[t[e+3]]+"-"+s[t[e+4]]+s[t[e+5]]+"-"+s[t[e+6]]+s[t[e+7]]+"-"+s[t[e+8]]+s[t[e+9]]+"-"+s[t[e+10]]+s[t[e+11]]+s[t[e+12]]+s[t[e+13]]+s[t[e+14]]+s[t[e+15]]).toLowerCase()}(n)}function g(t,e){t["app.method.args.count"]=e.length.toString(),t["app.method.args.types"]=e.map(t=>typeof t).join(",")}function y(t,e){t["app.method.result.type"]=typeof e,t["app.method.result.isArray"]=Array.isArray(e).toString(),t["app.method.result.length"]=Array.isArray(e)?e.length.toString():"1"}function d(t,e,r="http.request.body"){if(t[`${r}.exists`]=(!!e).toString(),t[`${r}.type`]=typeof e,e&&"object"==typeof e){const n=Object.keys(e);t[`${r}.keys`]=n.join(","),t[`${r}.keyCount`]=n.length.toString()}Array.isArray(e)&&(t[`${r}.isArray`]="true",t[`${r}.length`]=e.length.toString())}function l(t,e,r=!1,n="http.response.body"){if(t[`${n}.exists`]=(!!e).toString(),t[`${n}.type`]=typeof e,e&&"object"==typeof e){const r=Object.keys(e);t[`${n}.keys`]=r.join(","),t[`${n}.keyCount`]=r.length.toString()}Array.isArray(e)&&(t[`${n}.isArray`]="true",t[`${n}.length`]=e.length.toString()),r&&e&&(e.message&&(t["http.response.error.message"]=String(e.message).substring(0,500)),e.code&&(t["http.response.error.code"]=String(e.code)),e.error&&(t["http.response.error.type"]=String(e.error).substring(0,200)))}function f(t,e,r){if(e){if(t[`${r}.exists`]="true",t[`${r}.type`]=typeof e,"object"==typeof e&&!Array.isArray(e)){const n=Object.keys(e);t[`${r}.keys`]=n.join(","),t[`${r}.keyCount`]=n.length.toString()}Array.isArray(e)&&(t[`${r}.isArray`]="true",t[`${r}.length`]=e.length.toString(),e.length>0&&(t[`${r}.firstItemType`]=typeof e[0])),"string"==typeof e?t[`${r}.value`]=e.substring(0,200):"number"!=typeof e&&"boolean"!=typeof e||(t[`${r}.value`]=String(e))}else t[`${r}.exists`]="false"}function S(e){return function(r,n,o){const s=o?.value;if("function"!=typeof s)throw new Error("@Trace can only be applied to methods");o.value=async function(...o){const i=t.getStore();if(!i)return console.warn("[Trace] No active context. Skipping trace."),await s.apply(this,o);const a=u(),c=this?.constructor?.name||r.name||"UnknownClass",p=e?.name||`${c}.${n}`,d=process.hrtime.bigint(),l=(new Date).toISOString(),f=i.spanId,S={Timestamp:l,TraceId:i.traceId,SpanId:a,ParentSpanId:f,TraceState:"",ServiceName:i.serviceName,SpanName:p,SpanKind:e?.kind||"SPAN_KIND_INTERNAL",DurationUInt64:BigInt(0),StatusCode:"STATUS_CODE_UNSET",StatusMessage:"",SpanAttributes:{"code.function":n,"code.namespace":c,...e?.attributes},ResourceAttributes:i.traceContext.resourceAttributes,EventsNested:[],LinksNested:[]},m=i.spanId;i.spanId=a;try{g(S.SpanAttributes,o);const t=await s.apply(this,o);return y(S.SpanAttributes,t),S.StatusCode="STATUS_CODE_OK",t}catch(t){const e={Timestamp:(new Date).toISOString(),Name:"exception",Attributes:{"exception.type":t.constructor?.name||"Error","exception.message":t.message||"Unknown error","exception.stacktrace":String(t.stack||"").substring(0,5e3)}};throw S.EventsNested.push(e),S.StatusCode="STATUS_CODE_ERROR",S.StatusMessage=t.message||"Unhandled exception",t}finally{const t=process.hrtime.bigint();S.DurationUInt64=t-d,i.traceContext.spans.push(S),i.spanId=m}}}}export{S as Trace,e as TraceHeader,g as addArgsMetadata,f as addObjectMetadata,d as addRequestBodyMetadata,l as addResponseBodyMetadata,y as addResultMetadata,n as logger};
@@ -1 +1 @@
1
- "use strict";var t,e=require("@hemia/app-context");exports.TraceHeader=void 0,(t=exports.TraceHeader||(exports.TraceHeader={})).TRACE="x-trace-id",t.CORRELATION="x-correlation-id";const r={DEBUG:5,INFO:9,WARN:13,ERROR:17,FATAL:21};const n=new class{constructor(t){this.defaultServiceName=t}log(t,n,o){const s=e.asyncContext.getStore();if(!s)return console.warn("[Logger] No active context. Log not traced."),void console.log(`[${t}] ${n}`,o);const a={Timestamp:(new Date).toISOString(),TraceId:s.traceId,SpanId:s.spanId,TraceFlags:1,SeverityText:t,SeverityNumber:r[t],ServiceName:s.serviceName,Body:n,LogAttributes:this.stringifyAttributes(o||{}),ResourceAttributes:s.traceContext.resourceAttributes};s.traceContext.logs.push(a),"production"!==process.env.NODE_ENV&&console.log(`[${t}] [TraceId:${s.traceId.substring(0,8)}...] ${n}`,o||"")}stringifyAttributes(t){const e={};for(const[r,n]of Object.entries(t))if(null==n)e[r]="";else if("string"==typeof n)e[r]=n;else try{e[r]=JSON.stringify(n)}catch{e[r]=String(n)}return e}debug(t,e){this.log("DEBUG",t,e)}info(t,e){this.log("INFO",t,e)}warn(t,e){this.log("WARN",t,e)}error(t,e){this.log("ERROR",t,e)}fatal(t,e){this.log("FATAL",t,e)}};function o(t){try{const e=JSON.stringify(t,function(){const t=new WeakSet;return(e,r)=>{if(null===r)return null;if("object"==typeof r){if(t.has(r))return"[Circular]";t.add(r)}return"function"==typeof r?r.toString():r}}());return JSON.parse(e)}catch(e){return String(t)}}const s=["content-type","host","x-no-cookies","x-api-key","Authorization","origin"];function a(t){return t.map(t=>{if(function(t){return null!=t&&"object"==typeof t&&"string"==typeof t.method&&"string"==typeof t.url&&"object"==typeof t.headers}(t)){const e=s.reduce((e,r)=>{const n=r.toLowerCase();return t.headers[n]&&(e[n]=t.headers[n]),e},{});return{method:t.method,url:t.url,params:t.params,query:t.query,body:t.body,headers:e}}return function(t){return null!=t&&"object"==typeof t&&"number"==typeof t.statusCode&&"function"==typeof t.setHeader&&"function"==typeof t.end}(t)?"[ExpressResponseObject]":"function"==typeof t?"[Function next]":t})}for(var i,c=[],u=0;u<256;++u)c.push((u+256).toString(16).slice(1));var p=new Uint8Array(16);function d(){if(!i&&!(i="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return i(p)}var f={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function g(t,e,r){if(f.randomUUID&&!t)return f.randomUUID();var n=(t=t||{}).random||(t.rng||d)();return n[6]=15&n[6]|64,n[8]=63&n[8]|128,function(t,e=0){return(c[t[e+0]]+c[t[e+1]]+c[t[e+2]]+c[t[e+3]]+"-"+c[t[e+4]]+c[t[e+5]]+"-"+c[t[e+6]]+c[t[e+7]]+"-"+c[t[e+8]]+c[t[e+9]]+"-"+c[t[e+10]]+c[t[e+11]]+c[t[e+12]]+c[t[e+13]]+c[t[e+14]]+c[t[e+15]]).toLowerCase()}(n)}exports.Trace=function(t){return function(r,n,s){const i=s?.value;if("function"!=typeof i)throw new Error("@Trace can only be applied to methods");s.value=async function(...s){const c=e.asyncContext.getStore();if(!c)return console.warn("[Trace] No active context. Skipping trace."),await i.apply(this,s);const u=g(),p=this?.constructor?.name||r.name||"UnknownClass",d=t?.name||`${p}.${n}`,f=process.hrtime.bigint(),l=(new Date).toISOString(),y=c.spanId,m={Timestamp:l,TraceId:c.traceId,SpanId:u,ParentSpanId:y,TraceState:"",ServiceName:c.serviceName,SpanName:d,SpanKind:t?.kind||"SPAN_KIND_INTERNAL",DurationUInt64:BigInt(0),StatusCode:"STATUS_CODE_UNSET",StatusMessage:"",SpanAttributes:{"code.function":n,"code.namespace":p,...t?.attributes},ResourceAttributes:c.traceContext.resourceAttributes,EventsNested:[],LinksNested:[]},S=c.spanId;c.spanId=u;try{const t=a(s);m.SpanAttributes["app.method.args"]=o(t),m.SpanAttributes["app.method.args.count"]=s.length.toString();const e=await i.apply(this,s),r=o(a(Array.isArray(e)?e:[e]));return m.SpanAttributes["app.method.result"]=r.substring(0,2e3),m.StatusCode="STATUS_CODE_OK",e}catch(t){const e={Timestamp:(new Date).toISOString(),Name:"exception",Attributes:{"exception.type":t.constructor?.name||"Error","exception.message":t.message||"Unknown error","exception.stacktrace":(t.stack||"").substring(0,5e3)}};throw m.EventsNested.push(e),m.StatusCode="STATUS_CODE_ERROR",m.StatusMessage=t.message||"Unhandled exception",t}finally{const t=process.hrtime.bigint();m.DurationUInt64=t-f,c.traceContext.spans.push(m),c.spanId=S}}}},exports.logger=n;
1
+ "use strict";var t,e=require("@hemia/app-context");exports.TraceHeader=void 0,(t=exports.TraceHeader||(exports.TraceHeader={})).TRACE="x-trace-id",t.CORRELATION="x-correlation-id";const r={DEBUG:5,INFO:9,WARN:13,ERROR:17,FATAL:21};const n=new class{constructor(t){this.defaultServiceName=t}log(t,n,o){const s=e.asyncContext.getStore();if(!s)return console.warn("[Logger] No active context. Log not traced."),void console.log(`[${t}] ${n}`,o);const a={Timestamp:(new Date).toISOString(),TraceId:s.traceId,SpanId:s.spanId,TraceFlags:1,SeverityText:t,SeverityNumber:r[t],ServiceName:s.serviceName,Body:n,LogAttributes:this.stringifyAttributes(o||{}),ResourceAttributes:s.traceContext.resourceAttributes};s.traceContext.logs.push(a),"production"!==process.env.NODE_ENV&&console.log(`[${t}] [TraceId:${s.traceId.substring(0,8)}...] ${n}`,o||"")}stringifyAttributes(t){const e={};for(const[r,n]of Object.entries(t))if(null==n)e[r]="";else if("string"==typeof n)e[r]=n;else try{e[r]=JSON.stringify(n)}catch{e[r]=String(n)}return e}debug(t,e){this.log("DEBUG",t,e)}info(t,e){this.log("INFO",t,e)}warn(t,e){this.log("WARN",t,e)}error(t,e){this.log("ERROR",t,e)}fatal(t,e){this.log("FATAL",t,e)}};for(var o,s=[],a=0;a<256;++a)s.push((a+256).toString(16).slice(1));var i=new Uint8Array(16);function c(){if(!o&&!(o="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return o(i)}var p={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function u(t,e,r){if(p.randomUUID&&!t)return p.randomUUID();var n=(t=t||{}).random||(t.rng||c)();return n[6]=15&n[6]|64,n[8]=63&n[8]|128,function(t,e=0){return(s[t[e+0]]+s[t[e+1]]+s[t[e+2]]+s[t[e+3]]+"-"+s[t[e+4]]+s[t[e+5]]+"-"+s[t[e+6]]+s[t[e+7]]+"-"+s[t[e+8]]+s[t[e+9]]+"-"+s[t[e+10]]+s[t[e+11]]+s[t[e+12]]+s[t[e+13]]+s[t[e+14]]+s[t[e+15]]).toLowerCase()}(n)}function d(t,e){t["app.method.args.count"]=e.length.toString(),t["app.method.args.types"]=e.map(t=>typeof t).join(",")}function g(t,e){t["app.method.result.type"]=typeof e,t["app.method.result.isArray"]=Array.isArray(e).toString(),t["app.method.result.length"]=Array.isArray(e)?e.length.toString():"1"}exports.Trace=function(t){return function(r,n,o){const s=o?.value;if("function"!=typeof s)throw new Error("@Trace can only be applied to methods");o.value=async function(...o){const a=e.asyncContext.getStore();if(!a)return console.warn("[Trace] No active context. Skipping trace."),await s.apply(this,o);const i=u(),c=this?.constructor?.name||r.name||"UnknownClass",p=t?.name||`${c}.${n}`,y=process.hrtime.bigint(),l=(new Date).toISOString(),S=a.spanId,f={Timestamp:l,TraceId:a.traceId,SpanId:i,ParentSpanId:S,TraceState:"",ServiceName:a.serviceName,SpanName:p,SpanKind:t?.kind||"SPAN_KIND_INTERNAL",DurationUInt64:BigInt(0),StatusCode:"STATUS_CODE_UNSET",StatusMessage:"",SpanAttributes:{"code.function":n,"code.namespace":c,...t?.attributes},ResourceAttributes:a.traceContext.resourceAttributes,EventsNested:[],LinksNested:[]},h=a.spanId;a.spanId=i;try{d(f.SpanAttributes,o);const t=await s.apply(this,o);return g(f.SpanAttributes,t),f.StatusCode="STATUS_CODE_OK",t}catch(t){const e={Timestamp:(new Date).toISOString(),Name:"exception",Attributes:{"exception.type":t.constructor?.name||"Error","exception.message":t.message||"Unknown error","exception.stacktrace":String(t.stack||"").substring(0,5e3)}};throw f.EventsNested.push(e),f.StatusCode="STATUS_CODE_ERROR",f.StatusMessage=t.message||"Unhandled exception",t}finally{const t=process.hrtime.bigint();f.DurationUInt64=t-y,a.traceContext.spans.push(f),a.spanId=h}}}},exports.addArgsMetadata=d,exports.addObjectMetadata=function(t,e,r){if(e){if(t[`${r}.exists`]="true",t[`${r}.type`]=typeof e,"object"==typeof e&&!Array.isArray(e)){const n=Object.keys(e);t[`${r}.keys`]=n.join(","),t[`${r}.keyCount`]=n.length.toString()}Array.isArray(e)&&(t[`${r}.isArray`]="true",t[`${r}.length`]=e.length.toString(),e.length>0&&(t[`${r}.firstItemType`]=typeof e[0])),"string"==typeof e?t[`${r}.value`]=e.substring(0,200):"number"!=typeof e&&"boolean"!=typeof e||(t[`${r}.value`]=String(e))}else t[`${r}.exists`]="false"},exports.addRequestBodyMetadata=function(t,e,r="http.request.body"){if(t[`${r}.exists`]=(!!e).toString(),t[`${r}.type`]=typeof e,e&&"object"==typeof e){const n=Object.keys(e);t[`${r}.keys`]=n.join(","),t[`${r}.keyCount`]=n.length.toString()}Array.isArray(e)&&(t[`${r}.isArray`]="true",t[`${r}.length`]=e.length.toString())},exports.addResponseBodyMetadata=function(t,e,r=!1,n="http.response.body"){if(t[`${n}.exists`]=(!!e).toString(),t[`${n}.type`]=typeof e,e&&"object"==typeof e){const r=Object.keys(e);t[`${n}.keys`]=r.join(","),t[`${n}.keyCount`]=r.length.toString()}Array.isArray(e)&&(t[`${n}.isArray`]="true",t[`${n}.length`]=e.length.toString()),r&&e&&(e.message&&(t["http.response.error.message"]=String(e.message).substring(0,500)),e.code&&(t["http.response.error.code"]=String(e.code)),e.error&&(t["http.response.error.type"]=String(e.error).substring(0,200)))},exports.addResultMetadata=g,exports.logger=n;
@@ -3,3 +3,4 @@ export { TraceContext } from "./types/traceContext";
3
3
  export { TraceOptions } from "./types/traceOptions";
4
4
  export { logger } from "./log/Logger";
5
5
  export { Trace } from "./decorator/Traceable";
6
+ export { addArgsMetadata, addResultMetadata, addRequestBodyMetadata, addResponseBodyMetadata, addObjectMetadata } from "./utils/SpanAttributesHelper";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Utilidades para agregar metadata de objetos a SpanAttributes
3
+ * siguiendo el principio de metadata sobre serialización completa
4
+ */
5
+ /**
6
+ * Agrega metadata de argumentos de entrada a los SpanAttributes
7
+ * @param attributes - Objeto de atributos donde agregar metadata
8
+ * @param args - Array de argumentos a analizar
9
+ */
10
+ export declare function addArgsMetadata(attributes: Record<string, string>, args: any[]): void;
11
+ /**
12
+ * Agrega metadata de resultado/output a los SpanAttributes
13
+ * @param attributes - Objeto de atributos donde agregar metadata
14
+ * @param result - Resultado a analizar
15
+ */
16
+ export declare function addResultMetadata(attributes: Record<string, string>, result: any): void;
17
+ /**
18
+ * Agrega metadata de Request body a SpanAttributes (para middlewares HTTP)
19
+ * @param attributes - Objeto de atributos donde agregar metadata
20
+ * @param body - Body del request
21
+ * @param prefix - Prefijo para las keys (default: 'http.request.body')
22
+ */
23
+ export declare function addRequestBodyMetadata(attributes: Record<string, string>, body: any, prefix?: string): void;
24
+ /**
25
+ * Agrega metadata de Response body a SpanAttributes (para middlewares HTTP)
26
+ * Solo agrega información extendida en caso de errores
27
+ * @param attributes - Objeto de atributos donde agregar metadata
28
+ * @param body - Body del response
29
+ * @param isError - Si es una respuesta de error (statusCode >= 400)
30
+ * @param prefix - Prefijo para las keys (default: 'http.response.body')
31
+ */
32
+ export declare function addResponseBodyMetadata(attributes: Record<string, string>, body: any, isError?: boolean, prefix?: string): void;
33
+ /**
34
+ * Agrega metadata genérica de un objeto a SpanAttributes
35
+ * Útil para analizar cualquier tipo de payload
36
+ * @param attributes - Objeto de atributos donde agregar metadata
37
+ * @param data - Objeto a analizar
38
+ * @param prefix - Prefijo para las keys
39
+ */
40
+ export declare function addObjectMetadata(attributes: Record<string, string>, data: any, prefix: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hemia/trace-manager",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Gestor de trazas para registrar logs, errores y evento",
5
5
  "main": "dist/hemia-trace-manager.js",
6
6
  "module": "dist/hemia-trace-manager.esm.js",
@@ -13,8 +13,7 @@
13
13
  "test": "jest --passWithNoTests --detectOpenHandles"
14
14
  },
15
15
  "devDependencies": {
16
- "@hemia/app-context": "^0.0.3",
17
- "@hemia/db-manager": "^0.0.6",
16
+ "@hemia/app-context": "^0.0.6",
18
17
  "@rollup/plugin-commonjs": "^26.0.1",
19
18
  "@rollup/plugin-json": "^6.1.0",
20
19
  "@rollup/plugin-node-resolve": "^15.2.3",