@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.
- package/README.md +313 -59
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1,116 +1,370 @@
|
|
|
1
1
|
# @hemia/trace-manager
|
|
2
2
|
|
|
3
|
-
Sistema de
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
####
|
|
68
|
+
#### **Opciones de configuración:**
|
|
22
69
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
### ✅
|
|
100
|
+
### ✅ Logger Contextual
|
|
41
101
|
|
|
42
|
-
|
|
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
|
-
####
|
|
104
|
+
#### **Características:**
|
|
45
105
|
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
##
|
|
273
|
+
## 🔍 Ejemplo completo
|
|
77
274
|
|
|
78
275
|
```ts
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
##
|
|
336
|
+
## 🛠️ Utilidades
|
|
103
337
|
|
|
104
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|