@hemia/trace-manager 0.0.2 → 0.0.3

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.
Files changed (2) hide show
  1. package/README.md +313 -59
  2. package/package.json +1 -2
package/README.md CHANGED
@@ -1,116 +1,370 @@
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:**
34
+
35
+ - 🔹 Crea span automáticamente con `SpanId` único
36
+ - 🔹 Captura argumentos de entrada en `SpanAttributes.app.method.args`
37
+ - 🔹 Captura resultado de retorno en `SpanAttributes.app.method.result`
38
+ - 🔹 Registra excepciones como eventos (`EventsNested`)
39
+ - 🔹 Calcula duración en nanosegundos (`DurationUInt64`)
40
+ - 🔹 Propaga contexto automáticamente a métodos hijos
41
+
42
+ #### **Uso básico:**
16
43
 
17
- ### ✅ `TraceManager<T>`
44
+ ```ts
45
+ import { Trace } from '@hemia/trace-manager';
46
+
47
+ class UserService {
48
+ @Trace()
49
+ async createUser(userData: any) {
50
+ // El decorador captura automáticamente:
51
+ // - Input: userData
52
+ // - Output: usuario creado
53
+ // - Duración de ejecución
54
+ // - Excepciones si ocurren
55
+
56
+ const user = await this.repository.save(userData);
57
+ return user;
58
+ }
18
59
 
19
- Clase encargada de registrar y consultar trazas desde un repositorio MongoDB usando `NoSQLRepository`.
60
+ @Trace({ name: 'validate-user-email' })
61
+ private async validateEmail(email: string) {
62
+ // Span hijo automático (hereda ParentSpanId)
63
+ return await this.emailValidator.check(email);
64
+ }
65
+ }
66
+ ```
20
67
 
21
- #### Métodos disponibles:
68
+ #### **Opciones de configuración:**
22
69
 
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
70
+ ```ts
71
+ interface TraceOptions {
72
+ name?: string; // Nombre personalizado del span (default: ClassName.methodName)
73
+ kind?: 'SPAN_KIND_INTERNAL' // Tipo de span: INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER
74
+ | 'SPAN_KIND_SERVER'
75
+ | 'SPAN_KIND_CLIENT'
76
+ | 'SPAN_KIND_PRODUCER'
77
+ | 'SPAN_KIND_CONSUMER';
78
+ attributes?: Record<string, string>; // Atributos personalizados
79
+ }
80
+ ```
30
81
 
31
- #### Ejemplo:
82
+ #### **Ejemplo avanzado:**
32
83
 
33
84
  ```ts
34
- const manager = new TraceManager<TraceDocument>(traceRepo);
35
- await manager.save('trace-id-123', { message: 'Evento recibido' });
85
+ class PaymentService {
86
+ @Trace({
87
+ name: 'process-payment-stripe',
88
+ kind: 'SPAN_KIND_CLIENT',
89
+ attributes: { 'payment.provider': 'stripe' }
90
+ })
91
+ async processPayment(amount: number, currency: string) {
92
+ // Span con atributos personalizados
93
+ return await this.stripeClient.charge(amount, currency);
94
+ }
95
+ }
36
96
  ```
37
97
 
38
98
  ---
39
99
 
40
- ### ✅ Decorador `@Traceable()`
100
+ ### ✅ Logger Contextual
41
101
 
42
- Permite trazar automáticamente métodos con input/output y errores.
102
+ Logger que automáticamente correlaciona logs con el `traceId` y `spanId` activo, permitiendo rastrear logs específicos dentro de una traza distribuida.
43
103
 
44
- #### Uso:
104
+ #### **Características:**
45
105
 
46
- ```ts
47
- import { Traceable } from '@hemia/trace-manager';
106
+ - 🔹 Correlación automática con `TraceId` y `SpanId`
107
+ - 🔹 Niveles de severidad: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`
108
+ - 🔹 Atributos personalizados en cada log
109
+ - 🔹 Compatible con formato OpenTelemetry para ClickHouse
110
+
111
+ #### **Uso:**
48
112
 
49
- class MiServicio {
50
- @Traceable({ ck: 'MiServicio', fn: 'procesar' })
51
- async procesar(data: any) {
52
- // lógica
53
- return { ok: true };
113
+ ```ts
114
+ import { logger } from '@hemia/trace-manager';
115
+
116
+ class OrderService {
117
+ @Trace()
118
+ async placeOrder(order: Order) {
119
+ logger.info('Processing order', { orderId: order.id, amount: order.total });
120
+
121
+ try {
122
+ const result = await this.payment.charge(order.total);
123
+ logger.info('Payment successful', { transactionId: result.id });
124
+ return result;
125
+ } catch (error) {
126
+ logger.error('Payment failed', { error: error.message });
127
+ throw error;
128
+ }
54
129
  }
55
130
  }
56
131
  ```
57
132
 
58
- * Input se traza al inicio
59
- * Output se traza al finalizar
60
- * Error se traza si ocurre una excepción
133
+ #### **Métodos disponibles:**
134
+
135
+ ```ts
136
+ logger.debug(message: string, attributes?: Record<string, any>): void
137
+ logger.info(message: string, attributes?: Record<string, any>): void
138
+ logger.warn(message: string, attributes?: Record<string, any>): void
139
+ logger.error(message: string, attributes?: Record<string, any>): void
140
+ logger.fatal(message: string, attributes?: Record<string, any>): void
141
+ ```
142
+
143
+ ---
144
+
145
+ ## 🏗️ Estructura de Datos
146
+
147
+ ### **TraceSpan** (Span de OpenTelemetry)
148
+
149
+ Mapea directamente con la tabla `otel_traces` de ClickHouse:
150
+
151
+ ```ts
152
+ interface TraceSpan {
153
+ Timestamp: string; // ISO 8601 - DateTime64(9)
154
+ TraceId: string; // ID único de la traza
155
+ SpanId: string; // ID único del span
156
+ ParentSpanId: string; // ID del span padre (jerarquía)
157
+ TraceState: string; // Estado de propagación W3C
158
+ ServiceName: string; // Nombre del servicio
159
+ SpanName: string; // Ej: "UserService.createUser"
160
+ SpanKind: 'SPAN_KIND_INTERNAL' | 'SPAN_KIND_SERVER' | 'SPAN_KIND_CLIENT' | ...;
161
+ DurationUInt64: bigint; // Duración en nanosegundos
162
+ StatusCode: 'STATUS_CODE_OK' | 'STATUS_CODE_ERROR' | 'STATUS_CODE_UNSET';
163
+ StatusMessage: string;
164
+ SpanAttributes: Record<string, string>; // app.method.args, app.method.result, etc.
165
+ ResourceAttributes: Record<string, string>; // host.name, service.name, etc.
166
+ EventsNested: SpanEvent[]; // Excepciones, logs puntuales
167
+ LinksNested: SpanLink[]; // Enlaces a otras trazas
168
+ }
169
+ ```
170
+
171
+ ### **TraceLog** (Log de OpenTelemetry)
172
+
173
+ Mapea directamente con la tabla `otel_logs` de ClickHouse:
174
+
175
+ ```ts
176
+ interface TraceLog {
177
+ Timestamp: string; // ISO 8601
178
+ TraceId: string; // Correlación automática con span activo
179
+ SpanId: string; // Span donde ocurrió el log
180
+ TraceFlags: number; // 1 = sampled
181
+ SeverityText: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
182
+ SeverityNumber: number; // 5, 9, 13, 17, 21
183
+ ServiceName: string;
184
+ Body: string; // Mensaje del log
185
+ LogAttributes: Record<string, string>; // Atributos custom
186
+ ResourceAttributes: Record<string, string>; // Metadata del servicio
187
+ }
188
+ ```
61
189
 
62
190
  ---
63
191
 
64
- ### `addTrace()` y `endTrace()`
192
+ ## 🔗 Integración con `@hemia/app-context`
193
+
194
+ 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.
65
195
 
66
- Funciones internas para agregar nodos de traza al contexto.
196
+ ### **Requisitos:**
197
+
198
+ El contexto debe ser inicializado previamente usando `@hemia/app-context` (generalmente en un middleware HTTP):
67
199
 
68
200
  ```ts
69
- const spanId = addTrace('Login', 'authUser', 'input', { email }, 'Inicio de login');
70
- // lógica
71
- endTrace(spanId, true, { userId });
201
+ import { asyncContext } from '@hemia/app-context';
202
+
203
+ // En tu middleware o inicialización
204
+ const context = {
205
+ traceId: generateTraceId(),
206
+ spanId: generateSpanId(),
207
+ serviceName: 'user-service',
208
+ traceContext: {
209
+ resourceAttributes: {
210
+ 'service.name': 'user-service',
211
+ 'host.name': os.hostname(),
212
+ },
213
+ spans: [],
214
+ logs: []
215
+ }
216
+ };
217
+
218
+ await asyncContext.run(context, async () => {
219
+ // Todo el código dentro hereda este contexto automáticamente
220
+ await handleRequest(req, res);
221
+ });
222
+ ```
223
+
224
+ ---
225
+
226
+ ## 📊 Esquema ClickHouse
227
+
228
+ ### Tabla: `otel_traces`
229
+
230
+ ```sql
231
+ CREATE TABLE otel_traces (
232
+ Timestamp DateTime64(9) CODEC(Delta, ZSTD(1)),
233
+ TraceId String CODEC(ZSTD(1)),
234
+ SpanId String CODEC(ZSTD(1)),
235
+ ParentSpanId String CODEC(ZSTD(1)),
236
+ ServiceName LowCardinality(String) CODEC(ZSTD(1)),
237
+ SpanName LowCardinality(String) CODEC(ZSTD(1)),
238
+ SpanKind LowCardinality(String) CODEC(ZSTD(1)),
239
+ DurationUInt64 UInt64 CODEC(ZSTD(1)),
240
+ StatusCode LowCardinality(String) CODEC(ZSTD(1)),
241
+ SpanAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
242
+ ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
243
+ EventsNested Nested (
244
+ Timestamp DateTime64(9),
245
+ Name LowCardinality(String),
246
+ Attributes Map(LowCardinality(String), String)
247
+ ) CODEC(ZSTD(1))
248
+ ) ENGINE = MergeTree()
249
+ PARTITION BY toDate(Timestamp)
250
+ ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp), TraceId);
251
+ ```
252
+
253
+ ### Tabla: `otel_logs`
254
+
255
+ ```sql
256
+ CREATE TABLE otel_logs (
257
+ Timestamp DateTime64(9) CODEC(Delta, ZSTD(1)),
258
+ TraceId String CODEC(ZSTD(1)),
259
+ SpanId String CODEC(ZSTD(1)),
260
+ SeverityText LowCardinality(String) CODEC(ZSTD(1)),
261
+ SeverityNumber Int32 CODEC(ZSTD(1)),
262
+ ServiceName LowCardinality(String) CODEC(ZSTD(1)),
263
+ Body String CODEC(ZSTD(1)),
264
+ LogAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
265
+ ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1))
266
+ ) ENGINE = MergeTree()
267
+ PARTITION BY toDate(Timestamp)
268
+ ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId);
72
269
  ```
73
270
 
74
271
  ---
75
272
 
76
- ## Tipos esperados
273
+ ## 🔍 Ejemplo completo
77
274
 
78
275
  ```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;
276
+ import { Trace, logger } from '@hemia/trace-manager';
277
+ import { asyncContext } from '@hemia/app-context';
278
+
279
+ class OrderController {
280
+ @Trace({ name: 'http-create-order', kind: 'SPAN_KIND_SERVER' })
281
+ async createOrder(req: Request) {
282
+ logger.info('Order request received', { userId: req.userId });
283
+
284
+ const order = await this.orderService.create(req.body);
285
+
286
+ logger.info('Order created successfully', { orderId: order.id });
287
+ return order;
288
+ }
289
+ }
290
+
291
+ class OrderService {
292
+ @Trace()
293
+ async create(orderData: any) {
294
+ // Span hijo automático (ParentSpanId = span del controller)
295
+ logger.debug('Validating order data');
296
+
297
+ await this.validate(orderData);
298
+ const order = await this.repository.save(orderData);
299
+
300
+ logger.info('Order persisted', { orderId: order.id });
301
+ return order;
302
+ }
303
+
304
+ @Trace({ name: 'validate-order-rules' })
305
+ private async validate(data: any) {
306
+ // Span nieto (hijo del método create)
307
+ if (!data.items?.length) {
308
+ logger.error('Validation failed: no items');
309
+ throw new Error('Order must have items');
310
+ }
311
+ }
97
312
  }
98
313
  ```
99
314
 
315
+ **Resultado en ClickHouse:**
316
+
317
+ 3 spans jerárquicos:
318
+ 1. `http-create-order` (root, SPAN_KIND_SERVER)
319
+ 2. `OrderService.create` (child, SPAN_KIND_INTERNAL)
320
+ 3. `validate-order-rules` (grandchild, SPAN_KIND_INTERNAL)
321
+
322
+ Y múltiples logs correlacionados con el mismo `TraceId`.
323
+
324
+ ---
325
+
326
+ ## 📝 Buenas Prácticas
327
+
328
+ 1. **Usar `@Trace()` en capas de negocio críticas**: Controllers, Services, Repositories
329
+ 2. **Logger en puntos de decisión**: Validaciones, llamadas externas, errores
330
+ 3. **Atributos significativos**: Agregar IDs de entidades, estados, providers
331
+ 4. **Nombres descriptivos**: Usar `name` en `@Trace()` para operaciones complejas
332
+ 5. **Sanitización automática**: Los inputs/outputs se sanitizan para evitar datos sensibles
333
+
100
334
  ---
101
335
 
102
- ## Requisitos
336
+ ## 🛠️ Utilidades
103
337
 
104
- * `@hemia/app-context` para contexto y persistencia
338
+ ### `sanitizeArgs()` y `toSafeJSON()`
339
+
340
+ Utilidades internas que previenen:
341
+ - Serialización de objetos circulares
342
+ - Exposición de contraseñas/tokens en atributos
343
+ - Overflow de tamaño en ClickHouse
344
+
345
+ ---
346
+
347
+ ## 📚 Dependencias
348
+
349
+ - `@hemia/app-context` - Manejo de contexto con AsyncLocalStorage
350
+ - `uuid` - Generación de IDs únicos para spans
105
351
 
106
352
  ---
107
353
 
108
- ## Licencia
354
+ ## 📄 Licencia
109
355
 
110
356
  MIT
111
357
 
112
358
  ---
113
359
 
114
- ## Autor
360
+ ## 👨‍💻 Autor
361
+
362
+ **Hemia Technologies**
363
+
364
+ ---
365
+
366
+ ## 🔗 Recursos
115
367
 
116
- Hemia Technologies
368
+ - [OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel/)
369
+ - [ClickHouse Documentation](https://clickhouse.com/docs)
370
+ - [AsyncLocalStorage Node.js](https://nodejs.org/api/async_context.html)
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.3",
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",
@@ -14,7 +14,6 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@hemia/app-context": "^0.0.3",
17
- "@hemia/db-manager": "^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",