npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@hemia/trace-manager

v0.0.4

Published

Gestor de trazas para registrar logs, errores y evento

Downloads

331

Readme

@hemia/trace-manager

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.


🚀 Características

  • Decorador @Trace - Instrumentación automática de métodos con captura de inputs/outputs
  • AsyncLocalStorage - Propagación automática de contexto sin pasar parámetros manualmente
  • Logger Contextual - Logs automáticamente correlacionados con traceId y spanId
  • Formato OpenTelemetry - Compatible con estándares de observabilidad
  • ClickHouse Ready - Esquema optimizado para análisis en ClickHouse
  • Captura de Errores - Excepciones registradas como eventos en spans
  • Jerarquía de Spans - Parent-Child spans automáticos por nivel de invocación

📦 Instalación

npm install @hemia/trace-manager @hemia/app-context

🎯 Componentes principales

✅ Decorador @Trace()

Instrumenta automáticamente métodos creando spans de OpenTelemetry con captura de inputs, outputs y errores.

Características:

  • 🔹 Crea span automáticamente con SpanId único
  • 🔹 Captura metadata de argumentos (tipos, cantidad) - no valores completos
  • 🔹 Captura metadata de resultados (tipo, isArray, length) - no valores completos
  • 🔹 Registra excepciones como eventos (EventsNested)
  • 🔹 Calcula duración en nanosegundos (DurationUInt64)
  • 🔹 Propaga contexto automáticamente a métodos hijos
  • 🔹 Privacy-first: No serializa valores sensibles, solo metadata

Uso básico:

import { Trace } from '@hemia/trace-manager';

class UserService {
  @Trace()
  async createUser(userData: any) {
    // El decorador captura automáticamente:
    // - Metadata de input: tipo, cantidad de args (NO valores completos)
    // - Metadata de output: tipo, isArray, length (NO valores completos)
    // - Duración de ejecución en nanosegundos
    // - Excepciones si ocurren (con stacktrace)
    
    const user = await this.repository.save(userData);
    return user;
  }

  @Trace({ name: 'validate-user-email' })
  private async validateEmail(email: string) {
    // Span hijo automático (hereda ParentSpanId)
    return await this.emailValidator.check(email);
  }
}

Opciones de configuración:

interface TraceOptions {
  name?: string;                    // Nombre personalizado del span (default: ClassName.methodName)
  kind?: 'SPAN_KIND_INTERNAL'       // Tipo de span: INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER
       | 'SPAN_KIND_SERVER' 
       | 'SPAN_KIND_CLIENT'
       | 'SPAN_KIND_PRODUCER'
       | 'SPAN_KIND_CONSUMER';
  attributes?: Record<string, string>; // Atributos personalizados
}

Ejemplo avanzado:

class PaymentService {
  @Trace({ 
    name: 'process-payment-stripe',
    kind: 'SPAN_KIND_CLIENT',
    attributes: { 'payment.provider': 'stripe' }
  })
  async processPayment(amount: number, currency: string) {
    // Span con atributos personalizados
    return await this.stripeClient.charge(amount, currency);
  }
}

✅ Logger Contextual

Logger que automáticamente correlaciona logs con el traceId y spanId activo, permitiendo rastrear logs específicos dentro de una traza distribuida.

Características:

  • 🔹 Correlación automática con TraceId y SpanId
  • 🔹 Niveles de severidad: DEBUG, INFO, WARN, ERROR, FATAL
  • 🔹 Atributos personalizados en cada log
  • 🔹 Compatible con formato OpenTelemetry para ClickHouse

Uso:

import { logger } from '@hemia/trace-manager';

class OrderService {
  @Trace()
  async placeOrder(order: Order) {
    logger.info('Processing order', { orderId: order.id, amount: order.total });
    
    try {
      const result = await this.payment.charge(order.total);
      logger.info('Payment successful', { transactionId: result.id });
      return result;
    } catch (error) {
      logger.error('Payment failed', { error: error.message });
      throw error;
    }
  }
}

Métodos disponibles:

logger.debug(message: string, attributes?: Record<string, any>): void
logger.info(message: string, attributes?: Record<string, any>): void
logger.warn(message: string, attributes?: Record<string, any>): void
logger.error(message: string, attributes?: Record<string, any>): void
logger.fatal(message: string, attributes?: Record<string, any>): void

🏗️ Estructura de Datos

TraceSpan (Span de OpenTelemetry)

Mapea directamente con la tabla otel_traces de ClickHouse:

interface TraceSpan {
  Timestamp: string;               // ISO 8601 - DateTime64(9)
  TraceId: string;                 // ID único de la traza
  SpanId: string;                  // ID único del span
  ParentSpanId: string;            // ID del span padre (jerarquía)
  TraceState: string;              // Estado de propagación W3C
  ServiceName: string;             // Nombre del servicio
  SpanName: string;                // Ej: "UserService.createUser"
  SpanKind: 'SPAN_KIND_INTERNAL' | 'SPAN_KIND_SERVER' | 'SPAN_KIND_CLIENT' | ...;
  DurationUInt64: bigint;          // Duración en nanosegundos
  StatusCode: 'STATUS_CODE_OK' | 'STATUS_CODE_ERROR' | 'STATUS_CODE_UNSET';
  StatusMessage: string;
  SpanAttributes: Record<string, string>;        // app.method.args, app.method.result, etc.
  ResourceAttributes: Record<string, string>;    // host.name, service.name, etc.
  EventsNested: SpanEvent[];       // Excepciones, logs puntuales
  LinksNested: SpanLink[];         // Enlaces a otras trazas
}

TraceLog (Log de OpenTelemetry)

Mapea directamente con la tabla otel_logs de ClickHouse:

interface TraceLog {
  Timestamp: string;               // ISO 8601
  TraceId: string;                 // Correlación automática con span activo
  SpanId: string;                  // Span donde ocurrió el log
  TraceFlags: number;              // 1 = sampled
  SeverityText: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
  SeverityNumber: number;          // 5, 9, 13, 17, 21
  ServiceName: string;
  Body: string;                    // Mensaje del log
  LogAttributes: Record<string, string>;         // Atributos custom
  ResourceAttributes: Record<string, string>;    // Metadata del servicio
}

🔗 Integración con @hemia/app-context

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.

Requisitos:

El contexto debe ser inicializado previamente usando @hemia/app-context (generalmente en un middleware HTTP):

import { asyncContext } from '@hemia/app-context';

// En tu middleware o inicialización
const context = {
  traceId: generateTraceId(),
  spanId: generateSpanId(),
  serviceName: 'user-service',
  traceContext: {
    resourceAttributes: {
      'service.name': 'user-service',
      'host.name': os.hostname(),
    },
    spans: [],
    logs: []
  }
};

await asyncContext.run(context, async () => {
  // Todo el código dentro hereda este contexto automáticamente
  await handleRequest(req, res);
});

📊 Esquema ClickHouse

Tabla: otel_traces

CREATE TABLE otel_traces (
    Timestamp DateTime64(9) CODEC(Delta, ZSTD(1)),
    TraceId String CODEC(ZSTD(1)),
    SpanId String CODEC(ZSTD(1)),
    ParentSpanId String CODEC(ZSTD(1)),
    ServiceName LowCardinality(String) CODEC(ZSTD(1)),
    SpanName LowCardinality(String) CODEC(ZSTD(1)),
    SpanKind LowCardinality(String) CODEC(ZSTD(1)),
    DurationUInt64 UInt64 CODEC(ZSTD(1)),
    StatusCode LowCardinality(String) CODEC(ZSTD(1)),
    SpanAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
    ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
    EventsNested Nested (
        Timestamp DateTime64(9),
        Name LowCardinality(String),
        Attributes Map(LowCardinality(String), String)
    ) CODEC(ZSTD(1))
) ENGINE = MergeTree()
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp), TraceId);

Tabla: otel_logs

CREATE TABLE otel_logs (
    Timestamp DateTime64(9) CODEC(Delta, ZSTD(1)),
    TraceId String CODEC(ZSTD(1)),
    SpanId String CODEC(ZSTD(1)),
    SeverityText LowCardinality(String) CODEC(ZSTD(1)),
    SeverityNumber Int32 CODEC(ZSTD(1)),
    ServiceName LowCardinality(String) CODEC(ZSTD(1)),
    Body String CODEC(ZSTD(1)),
    LogAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
    ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1))
) ENGINE = MergeTree()
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId);

🔍 Ejemplo completo

import { Trace, logger } from '@hemia/trace-manager';
import { asyncContext } from '@hemia/app-context';

class OrderController {
  @Trace({ name: 'http-create-order', kind: 'SPAN_KIND_SERVER' })
  async createOrder(req: Request) {
    logger.info('Order request received', { userId: req.userId });
    
    const order = await this.orderService.create(req.body);
    
    logger.info('Order created successfully', { orderId: order.id });
    return order;
  }
}

class OrderService {
  @Trace()
  async create(orderData: any) {
    // Span hijo automático (ParentSpanId = span del controller)
    logger.debug('Validating order data');
    
    await this.validate(orderData);
    const order = await this.repository.save(orderData);
    
    logger.info('Order persisted', { orderId: order.id });
    return order;
  }

  @Trace({ name: 'validate-order-rules' })
  private async validate(data: any) {
    // Span nieto (hijo del método create)
    if (!data.items?.length) {
      logger.error('Validation failed: no items');
      throw new Error('Order must have items');
    }
  }
}

Resultado en ClickHouse:

3 spans jerárquicos:

  1. http-create-order (root, SPAN_KIND_SERVER)
  2. OrderService.create (child, SPAN_KIND_INTERNAL)
  3. validate-order-rules (grandchild, SPAN_KIND_INTERNAL)

Y múltiples logs correlacionados con el mismo TraceId.


📝 Buenas Prácticas

  1. Usar @Trace() en capas de negocio críticas: Controllers, Services, Repositories
  2. Logger en puntos de decisión: Validaciones, llamadas externas, errores
  3. Atributos significativos: Agregar IDs de entidades, estados, providers
  4. Nombres descriptivos: Usar name en @Trace() para operaciones complejas
  5. Metadata approach: El decorador captura metadata (tipos, cantidad) en lugar de valores completos
  6. Privacy by default: No se serializan valores sensibles como passwords o tokens

🛠️ Utilidades

Helpers para SpanAttributes (Metadata Approach)

El paquete incluye utilidades para agregar metadata en lugar de serializar datos completos, siguiendo las mejores prácticas de observabilidad:

addArgsMetadata(attributes, args)

Agrega metadata de argumentos de función:

import { addArgsMetadata } from '@hemia/trace-manager';

const args = ['[email protected]', 123, { name: 'John' }];
addArgsMetadata(span.SpanAttributes, args);

// Resultado:
// {
//   "app.method.args.count": "3",
//   "app.method.args.types": "string,number,object"
// }

addResultMetadata(attributes, result)

Agrega metadata de resultado de función:

import { addResultMetadata } from '@hemia/trace-manager';

const result = [{ id: 1 }, { id: 2 }];
addResultMetadata(span.SpanAttributes, result);

// Resultado:
// {
//   "app.method.result.type": "object",
//   "app.method.result.isArray": "true",
//   "app.method.result.length": "2"
// }

addRequestBodyMetadata(attributes, body, prefix?)

Agrega metadata de Request body (para middlewares HTTP):

import { addRequestBodyMetadata } from '@hemia/trace-manager';

const reqBody = { email: '[email protected]', password: '***' };
addRequestBodyMetadata(span.SpanAttributes, reqBody);

// Resultado:
// {
//   "http.request.body.exists": "true",
//   "http.request.body.type": "object",
//   "http.request.body.keys": "email,password",
//   "http.request.body.keyCount": "2"
// }

addResponseBodyMetadata(attributes, body, isError, prefix?)

Agrega metadata de Response body con captura inteligente de errores:

import { addResponseBodyMetadata } from '@hemia/trace-manager';

const errorBody = { 
  code: 'AUTH_ERROR', 
  message: 'Invalid credentials' 
};

addResponseBodyMetadata(
  span.SpanAttributes, 
  errorBody, 
  true // isError = true activa captura de mensaje
);

// Resultado:
// {
//   "http.response.body.exists": "true",
//   "http.response.body.type": "object",
//   "http.response.body.keys": "code,message",
//   "http.response.body.keyCount": "2",
//   "http.response.error.code": "AUTH_ERROR",
//   "http.response.error.message": "Invalid credentials"
// }

addObjectMetadata(attributes, data, prefix)

Agrega metadata genérica de cualquier objeto:

import { addObjectMetadata } from '@hemia/trace-manager';

const payload = { items: [1, 2, 3], total: 100 };
addObjectMetadata(span.SpanAttributes, payload, 'order.payload');

// Resultado:
// {
//   "order.payload.exists": "true",
//   "order.payload.type": "object",
//   "order.payload.keys": "items,total",
//   "order.payload.keyCount": "2"
// }

Uso en Middleware Custom

import { 
  addRequestBodyMetadata, 
  addResponseBodyMetadata 
} from '@hemia/trace-manager';

export const customMiddleware = () => {
  return (req: Request, res: Response, next: NextFunction) => {
    // ... inicializar span ...

    res.on('finish', () => {
      const isError = res.statusCode >= 400;

      if (isError) {
        // Capturar metadata en lugar de cuerpos completos
        addRequestBodyMetadata(
          span.SpanAttributes,
          req.body
        );

        addResponseBodyMetadata(
          span.SpanAttributes,
          res.locals.responseBody,
          isError // Captura mensaje de error si existe
        );
      }
    });

    next();
  };
};

Ventajas del Metadata Approach

Menor storage: 10-20 bytes vs potencialmente KB de JSON
Privacy by default: No serializa valores sensibles (passwords, tokens)
Mejor cardinality: Más eficiente para queries en ClickHouse
Suficiente para debugging: Keys y tipos son suficientes para diagnosticar
Consistencia: Mismo approach en decoradores y middlewares

sanitizeArgs() y toSafeJSON()

Utilidades legacy que previenen:

  • Serialización de objetos circulares
  • Filtrado de Request/Response de Express
  • Overflow de tamaño en ClickHouse

⚠️ Nota: El decorador @Trace ahora usa metadata en lugar de estas utilidades, pero siguen disponibles para compatibilidad.


📚 Dependencias

  • @hemia/app-context (^0.0.6) - Manejo de contexto con AsyncLocalStorage
  • uuid (^10.0.0) - Generación de IDs únicos para spans

🔄 Versión Actual

v0.0.3 - Metadata approach implementado (Noviembre 2025)


📄 Licencia

MIT


👨‍💻 Autor

Hemia Technologies


🔗 Recursos