Home/Docs/observability
OB

observability

v0.1.1

@backendkit-labs/observability

Resilient structured logging, Prometheus metrics, and OTel spans.

Overview#

Observability is not three separate tools (logs, metrics, tracing) bolted together — it is the ability to understand the internal state of a system from its external outputs. In practice, most NestJS apps mix Winston, Prometheus, and OpenTelemetry with complex, fragile configurations where a saturated log ingester can degrade or crash the main API.

@backendkit-labs/observability solves this with an integrated, resilient approach. A single ObservabilityModule.forRoot() import gives you:

  • Automatic correlation ID propagation via AsyncLocalStorage — every log line in the entire async chain gets the same correlationId without manual threading.
  • Structured Winston logging enriched with serviceName, environment, correlationId, traceId, and spanId automatically.
  • Resilient telemetry transport: logs and metrics are sent in batches with a circuit breaker — if the ingester fails, the API is not affected.
  • Fire-and-forget metrics: MetricsService calls are async and non-blocking — they never add latency to your request path.
  • AllExceptionsFilter + ErrorMapper: centralizes HTTP error responses and maps domain exceptions to specific status codes declaratively.

Module Setup#

npm install @backendkit-labs/observability
PropTypeDescription
serviceNamestringService name stamped on every log line, metric, and span.
environmentstringRuntime environment (development, staging, production).
logLevel'debug'|'info'|'warn'|'error'Minimum log level to output.
logTransport.urlstringHTTP endpoint to batch-ship logs. Protected by an internal circuit breaker.
logTransport.batchSizenumberNumber of log entries per batch.
metrics.enabledbooleanExpose a Prometheus /metrics HTTP scrape endpoint.
metrics.portnumberPort for the /metrics endpoint.
tracing.enabledbooleanEnable OpenTelemetry tracing with W3C TraceContext propagation.
tracing.exporterUrlstringOTLP gRPC endpoint for span export (e.g. http://otel-collector:4317).

LoggerService#

Inject LoggerService anywhere. The correlation ID is propagated automatically via AsyncLocalStorage through the entire async call chain — no manual thread-local setup, no parameter threading. When OpenTelemetry tracing is enabled, traceId and spanId are appended too, making logs and traces directly correlatable.

payment.service.ts
@Injectable()
export class PaymentService {
  constructor(private readonly logger: LoggerService) {}

  async charge(dto: ChargeDto) {
    this.logger.info('Processing charge', { userId: dto.userId, amount: dto.amount });
    // Output: { "level":"info", "message":"Processing charge",
    //           "userId":"usr_1", "amount":9900, "correlationId":"req-abc123",
    //           "traceId":"4bf92f3...", "spanId":"00f067aa0ba902b7" }

    // Available levels:
    this.logger.debug('Verbose detail');
    this.logger.warn('Unexpected but non-fatal condition');
    this.logger.error('Something failed', { error: someError });
  }
}

MetricsService#

All metric operations are fire-and-forget — they never block the request path. The @TrackPerformance decorator automatically measures method duration, creates an OpenTelemetry span, and records success/error counters.

payment.service.ts
// Automatic duration tracking + OTel span
@TrackPerformance({ operation: 'payment.charge' })
async charge(dto: ChargeDto): Promise<Result<Payment, PaymentError>> { ... }

// Manual metric recording
this.metrics.increment('payment.attempts', { currency: dto.currency });
this.metrics.increment('payment.failures', { reason: result.error.type });
this.metrics.histogram('payment.duration', durationMs, { status: 'ok' });
this.metrics.gauge('queue.depth', currentQueueSize);

OpenTelemetry#

When tracing.enabled is true, the module configures an OTLP gRPC exporter and propagates W3C TraceContext headers automatically on every outgoing request. Inject TracerService for manual span control.

payment.service.ts
@Injectable()
export class PaymentService {
  constructor(private readonly tracer: TracerService) {}

  async charge(dto: ChargeDto): Promise<Result<Payment, PaymentError>> {
    return this.tracer.startActiveSpan('payment.charge', async (span) => {
      span.setAttributes({ 'payment.userId': dto.userId, 'payment.amount': dto.amount });
      const result = await this.doCharge(dto);
      if (!result.ok) span.setStatus({ code: SpanStatusCode.ERROR, message: result.error.type });
      span.end();
      return result;
    });
  }
}

AllExceptionsFilter & ErrorMapper

Register AllExceptionsFilter globally to catch every unhandled exception and transform it into a consistent HTTP response shape. Use ErrorMapper to declaratively map domain errors to specific HTTP status codes — keeping business logic completely decoupled from HTTP concerns.

main.ts
import { AllExceptionsFilter, ErrorMapper } from '@backendkit-labs/observability';

const mapper = new ErrorMapper()
  .map('not_found',       404)
  .map('forbidden',       403)
  .map('payment_failed',  402)
  .map('validation_error', 422);

app.useGlobalFilters(new AllExceptionsFilter(mapper));

Examples#

From basic to production-grade — copy and adapt.

app.module.ts
import { ObservabilityModule } from '@backendkit-labs/observability';

@Module({
  imports: [
    ObservabilityModule.forRoot({
      serviceName: 'payment-api',
      environment: process.env.NODE_ENV ?? 'development',
      logLevel: 'info',
      metrics: { enabled: true, port: 9090 },
      tracing: { enabled: true, exporterUrl: 'http://otel-collector:4317' },
    }),
  ],
})
export class AppModule {}

⚖️ vs. Alternatives#

Comparing against building your own observability stack from individual packages — which is what most teams do today.

Feature@backendkit-labs/observabilitypino (alone)Custom (winston + prom-client + OTel)
Auto correlation ID✅ Zero config❌ Manual AsyncLocalStorage❌ Manual
NestJS forRoot() module
Prometheus /metrics✅ Built-in⚠️ Requires prom-client setup
OpenTelemetry tracing✅ Built-in⚠️ Requires @opentelemetry/* setup
@WithMetrics decorator
Structured JSON logging✅ Winston✅ JSON✅ Depends
Setup complexity✅ One import⚠️ Medium❌ High
Runtime dependencies~5 (all bundled)05–10+

✅ Supported  ·  ❌ Not supported  ·  ⚠️ Partial / workaround needed. Download counts are approximate weekly npm averages.