@hemia/trace-manager 0.0.3 → 0.0.5
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
|
@@ -33,11 +33,12 @@ Instrumenta automáticamente métodos creando spans de OpenTelemetry con captura
|
|
|
33
33
|
#### **Características:**
|
|
34
34
|
|
|
35
35
|
- 🔹 Crea span automáticamente con `SpanId` único
|
|
36
|
-
- 🔹 Captura
|
|
37
|
-
- 🔹 Captura
|
|
36
|
+
- 🔹 Captura **metadata** de argumentos (tipos, cantidad) - no valores completos
|
|
37
|
+
- 🔹 Captura **metadata** de resultados (tipo, isArray, length) - no valores completos
|
|
38
38
|
- 🔹 Registra excepciones como eventos (`EventsNested`)
|
|
39
39
|
- 🔹 Calcula duración en nanosegundos (`DurationUInt64`)
|
|
40
40
|
- 🔹 Propaga contexto automáticamente a métodos hijos
|
|
41
|
+
- 🔹 **Privacy-first**: No serializa valores sensibles, solo metadata
|
|
41
42
|
|
|
42
43
|
#### **Uso básico:**
|
|
43
44
|
|
|
@@ -48,10 +49,10 @@ class UserService {
|
|
|
48
49
|
@Trace()
|
|
49
50
|
async createUser(userData: any) {
|
|
50
51
|
// El decorador captura automáticamente:
|
|
51
|
-
// -
|
|
52
|
-
// -
|
|
53
|
-
// - Duración de ejecución
|
|
54
|
-
// - Excepciones si ocurren
|
|
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)
|
|
55
56
|
|
|
56
57
|
const user = await this.repository.save(userData);
|
|
57
58
|
return user;
|
|
@@ -329,25 +330,171 @@ Y múltiples logs correlacionados con el mismo `TraceId`.
|
|
|
329
330
|
2. **Logger en puntos de decisión**: Validaciones, llamadas externas, errores
|
|
330
331
|
3. **Atributos significativos**: Agregar IDs de entidades, estados, providers
|
|
331
332
|
4. **Nombres descriptivos**: Usar `name` en `@Trace()` para operaciones complejas
|
|
332
|
-
5. **
|
|
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
|
|
333
335
|
|
|
334
336
|
---
|
|
335
337
|
|
|
336
338
|
## 🛠️ Utilidades
|
|
337
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
|
+
|
|
338
479
|
### `sanitizeArgs()` y `toSafeJSON()`
|
|
339
480
|
|
|
340
|
-
Utilidades
|
|
481
|
+
Utilidades legacy que previenen:
|
|
341
482
|
- Serialización de objetos circulares
|
|
342
|
-
-
|
|
483
|
+
- Filtrado de Request/Response de Express
|
|
343
484
|
- Overflow de tamaño en ClickHouse
|
|
344
485
|
|
|
486
|
+
**⚠️ Nota**: El decorador `@Trace` ahora usa metadata en lugar de estas utilidades, pero siguen disponibles para compatibilidad.
|
|
487
|
+
|
|
345
488
|
---
|
|
346
489
|
|
|
347
490
|
## 📚 Dependencias
|
|
348
491
|
|
|
349
|
-
- `@hemia/app-context` - Manejo de contexto con AsyncLocalStorage
|
|
350
|
-
- `uuid` - Generación de IDs únicos para spans
|
|
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)
|
|
351
498
|
|
|
352
499
|
---
|
|
353
500
|
|
|
@@ -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
|
|
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=i.spanId,p=this?.constructor?.name||r.name||"UnknownClass",d=e?.name||`${p}.${n}`,l=process.hrtime.bigint(),f={Timestamp:(new Date).toISOString(),TraceId:i.traceId,SpanId:a,ParentSpanId:c,TraceState:"",ServiceName:i.serviceName,SpanName:d,SpanKind:e?.kind||"SPAN_KIND_INTERNAL",DurationUInt64:BigInt(0),StatusCode:"STATUS_CODE_UNSET",StatusMessage:"",SpanAttributes:{"code.function":n,"code.namespace":p,...e?.attributes},ResourceAttributes:i.traceContext.resourceAttributes,EventsNested:[],LinksNested:[]},S={...i,spanId:a};return t.run(S,async()=>{try{g(f.SpanAttributes,o);const t=await s.apply(this,o);return y(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,"exception.stacktrace":String(t.stack||"").substring(0,1e3)}};throw f.EventsNested.push(e),f.StatusCode="STATUS_CODE_ERROR",f.StatusMessage=t.message,t}finally{const t=process.hrtime.bigint();f.DurationUInt64=t-l,i.traceContext.spans.push(f)}})}}}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
|
|
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 o=new class{constructor(t){this.defaultServiceName=t}log(t,o,n){const s=e.asyncContext.getStore();if(!s)return console.warn("[Logger] No active context. Log not traced."),void console.log(`[${t}] ${o}`,n);const a={Timestamp:(new Date).toISOString(),TraceId:s.traceId,SpanId:s.spanId,TraceFlags:1,SeverityText:t,SeverityNumber:r[t],ServiceName:s.serviceName,Body:o,LogAttributes:this.stringifyAttributes(n||{}),ResourceAttributes:s.traceContext.resourceAttributes};s.traceContext.logs.push(a),"production"!==process.env.NODE_ENV&&console.log(`[${t}] [TraceId:${s.traceId.substring(0,8)}...] ${o}`,n||"")}stringifyAttributes(t){const e={};for(const[r,o]of Object.entries(t))if(null==o)e[r]="";else if("string"==typeof o)e[r]=o;else try{e[r]=JSON.stringify(o)}catch{e[r]=String(o)}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 n,s=[],a=0;a<256;++a)s.push((a+256).toString(16).slice(1));var i=new Uint8Array(16);function c(){if(!n&&!(n="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 n(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 o=(t=t||{}).random||(t.rng||c)();return o[6]=15&o[6]|64,o[8]=63&o[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()}(o)}function g(t,e){t["app.method.args.count"]=e.length.toString(),t["app.method.args.types"]=e.map(t=>typeof t).join(",")}function d(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,o,n){const s=n?.value;if("function"!=typeof s)throw new Error("@Trace can only be applied to methods");n.value=async function(...n){const a=e.asyncContext.getStore();if(!a)return console.warn("[Trace] No active context. Skipping trace."),await s.apply(this,n);const i=u(),c=a.spanId,p=this?.constructor?.name||r.name||"UnknownClass",y=t?.name||`${p}.${o}`,l=process.hrtime.bigint(),S={Timestamp:(new Date).toISOString(),TraceId:a.traceId,SpanId:i,ParentSpanId:c,TraceState:"",ServiceName:a.serviceName,SpanName:y,SpanKind:t?.kind||"SPAN_KIND_INTERNAL",DurationUInt64:BigInt(0),StatusCode:"STATUS_CODE_UNSET",StatusMessage:"",SpanAttributes:{"code.function":o,"code.namespace":p,...t?.attributes},ResourceAttributes:a.traceContext.resourceAttributes,EventsNested:[],LinksNested:[]},f={...a,spanId:i};return e.asyncContext.run(f,async()=>{try{g(S.SpanAttributes,n);const t=await s.apply(this,n);return d(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,"exception.stacktrace":String(t.stack||"").substring(0,1e3)}};throw S.EventsNested.push(e),S.StatusCode="STATUS_CODE_ERROR",S.StatusMessage=t.message,t}finally{const t=process.hrtime.bigint();S.DurationUInt64=t-l,a.traceContext.spans.push(S)}})}}},exports.addArgsMetadata=g,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 o=Object.keys(e);t[`${r}.keys`]=o.join(","),t[`${r}.keyCount`]=o.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 o=Object.keys(e);t[`${r}.keys`]=o.join(","),t[`${r}.keyCount`]=o.length.toString()}Array.isArray(e)&&(t[`${r}.isArray`]="true",t[`${r}.length`]=e.length.toString())},exports.addResponseBodyMetadata=function(t,e,r=!1,o="http.response.body"){if(t[`${o}.exists`]=(!!e).toString(),t[`${o}.type`]=typeof e,e&&"object"==typeof e){const r=Object.keys(e);t[`${o}.keys`]=r.join(","),t[`${o}.keyCount`]=r.length.toString()}Array.isArray(e)&&(t[`${o}.isArray`]="true",t[`${o}.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=d,exports.logger=o;
|
|
@@ -4,3 +4,9 @@ import { TraceOptions } from "../types/traceOptions";
|
|
|
4
4
|
* Usa AsyncLocalStorage para propagar contexto automáticamente
|
|
5
5
|
*/
|
|
6
6
|
export declare function Trace(options?: TraceOptions): <T extends (...args: any[]) => any>(target: any, key: string, descriptor: PropertyDescriptor) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Aplica @Trace automáticamente a todos los métodos de la clase
|
|
9
|
+
*/
|
|
10
|
+
export declare function Traceable(options?: {
|
|
11
|
+
exclude?: string[];
|
|
12
|
+
}): (constructor: Function) => void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.0.5",
|
|
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,7 +13,7 @@
|
|
|
13
13
|
"test": "jest --passWithNoTests --detectOpenHandles"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@hemia/app-context": "^0.0.
|
|
16
|
+
"@hemia/app-context": "^0.0.6",
|
|
17
17
|
"@rollup/plugin-commonjs": "^26.0.1",
|
|
18
18
|
"@rollup/plugin-json": "^6.1.0",
|
|
19
19
|
"@rollup/plugin-node-resolve": "^15.2.3",
|