Instrumentação Node.js com Elven Observability


Sumário

  • Visão geral
  • Pré-requisitos
  • Instalação
  • Quick Start — Zero-Code (recomendado)
  • Quick Start — Inicialização programática
  • Configuração por variáveis de ambiente
  • Guia por framework
  • Usando logger, tracer e metrics
  • Instrumentações automáticas
  • Configuração avançada
  • Integrações com bibliotecas de logging
  • Boas práticas
  • Deploy com Docker
  • Troubleshooting
  • FAQ

Visão geral

A instrumentação Node.js da Elven é composta por um pacote unificado que coordena dois componentes internos:

┌──────────────────────────────────────────────────────────┐
│            elven-unified-observability-js                 │
│                                                          │
│  ┌────────────────────────┐  ┌────────────────────────┐  │
│  │  Logs Interceptor      │  │  OTel Instrumentation  │  │
│  │                        │  │                        │  │
│  │  - Captura logs        │  │  - Traces distribuídos │  │
│  │  - Intercepta console  │  │  - Métricas            │  │
│  │  - Batching + retry    │  │  - Auto-instrumentação │  │
│  │  - Envia para Loki     │  │  - Exporta via OTLP    │  │
│  └───────────┬────────────┘  └───────────┬────────────┘  │
│              │                           │               │
└──────────────┼───────────────────────────┼───────────────┘
               │                           │
               ▼                           ▼
    Elven Loki Gateway            Elven OTLP Collector
         (logs)                  (traces & métricas)
Componente O que faz
elven-unified-observability-js Pacote principal — instale apenas este. Coordena bootstrap, config e lifecycle
elven-logs-interceptor Pipeline de logs: captura, filtra, faz batching e envia para Loki
elven-opentelemetry-instrumentation-js Distro OTel: configura traces, métricas, auto-instrumentação e privacy

Você só precisa instalar o pacote unificado. As dependências são resolvidas automaticamente.


Pré-requisitos

  • Node.js 18+
  • npm, yarn ou pnpm
  • Coletor OpenTelemetry (OTLP) implantado na sua infraestrutura (a Elven realiza o deploy do collector no seu ambiente — não é um endpoint compartilhado)
  • Credenciais da Elven Observability:
  • Tenant ID
  • API Token
  • Endpoint do seu Collector OTLP (para traces/métricas)
  • Endpoint Loki (para logs)

Instalação

npm install elven-unified-observability-js

Verificar instalação:

node -e "require('elven-unified-observability-js'); console.log('OK')"

Quick Start — Zero-Code (recomendado)

O modo zero-code instrumenta sua aplicação sem nenhuma alteração no código fonte. A lib usa o mecanismo --require do Node.js para inicializar tudo antes da sua app rodar.

1. Crie um arquivo .env na raiz do projeto

# === Identidade do serviço ===
LOGS_APP_NAME=meu-servico-api
LOGS_APP_VERSION=1.0.0
NODE_ENV=production

# === Telemetria (traces + métricas) ===
OTEL_SERVICE_NAME=meu-servico-api
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.sua-infra.com:4318
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp

# === Logs ===
LOGS_URL=https://loki.elvenobservability.com/loki/api/v1/push
LOGS_TENANT=seu-tenant-id
LOGS_TOKEN=seu-api-token
LOGS_COMPRESSION=gzip

# === Controle ===
ENABLE_LOGGING=true
ENABLE_METRICS=true
ENABLE_TRACING=true

2. Rode sua aplicação com --require

node --require elven-unified-observability-js/register app.js

Ou via NODE_OPTIONS (funciona com qualquer runner):

NODE_OPTIONS="--require elven-unified-observability-js/register" node app.js
NODE_OPTIONS="--require elven-unified-observability-js/register" npx ts-node app.ts
NODE_OPTIONS="--require elven-unified-observability-js/register" npx nest start
NODE_OPTIONS="--require elven-unified-observability-js/register" npx next start

Pronto! Traces, métricas e logs já estão sendo coletados.


Quick Start — Inicialização programática

Use esta abordagem quando precisar de controle sobre a configuração, ou quando não puder usar --require.

Importante: A chamada init() deve ser a primeira coisa a rodar na aplicação, antes de importar frameworks (Express, Fastify, etc.), para que o OpenTelemetry consiga instrumentar as bibliotecas corretamente.

Exemplo mínimo

import { init, logger, tracer, metrics } from "elven-unified-observability-js";

async function bootstrap() {
  await init({
    serviceName: "meu-servico-api",
    version: "1.0.0",
    environment: "production",

    logging: {
      transport: {
        url: "https://loki.elvenobservability.com/loki/api/v1/push",
        tenantId: "seu-tenant-id",
        authToken: "seu-api-token",
        compression: "gzip",
      },
    },

    telemetry: {
      tracing: { enabled: true },
      metrics: { enabled: true },
    },

    enableLogging: true,
    enableMetrics: true,
    enableTracing: true,
  });

  // Usar os singletons globais
  logger.info("aplicação iniciada", { version: "1.0.0" });
  metrics.getMeter("app").createCounter("startups_total").add(1);

  // Importar e iniciar sua app DEPOIS do init
  const { app } = await import("./app");
  app.listen(3000);
}

bootstrap();

Exemplo com JavaScript (CommonJS)

const { init, logger, tracer, metrics } = require("elven-unified-observability-js");

async function bootstrap() {
  await init({
    serviceName: "meu-servico",
    version: "1.0.0",
    environment: process.env.NODE_ENV || "production",

    logging: {
      transport: {
        url: process.env.LOGS_URL,
        tenantId: process.env.LOGS_TENANT,
        authToken: process.env.LOGS_TOKEN,
      },
    },

    enableLogging: true,
    enableTracing: true,
    enableMetrics: true,
  });

  logger.info("serviço pronto");

  // Iniciar app
  require("./app");
}

bootstrap();

Configuração por variáveis de ambiente

Identidade do serviço

Atributo 1a opção 2a opção Default
Nome LOGS_APP_NAME / LOKI_APP_NAME OTEL_SERVICE_NAME unknown-service
Versão LOGS_APP_VERSION / LOKI_APP_VERSION OTEL_SERVICE_VERSION 1.0.0
Ambiente LOGS_ENVIRONMENT / LOKI_ENVIRONMENT NODE_ENV development

Controle geral

Variável Descrição Default
ENABLE_LOGGING Habilitar pipeline de logs true
ENABLE_METRICS Habilitar métricas OTel true
ENABLE_TRACING Habilitar traces OTel true

Telemetria — Traces e Métricas

Variável Descrição Default
OTEL_EXPORTER_OTLP_ENDPOINT URL do coletor OTLP
OTEL_EXPORTER_OTLP_PROTOCOL Protocolo: http/protobuf ou grpc http/protobuf
OTEL_EXPORTER_OTLP_COMPRESSION Compressão: gzip ou none none
OTEL_TRACES_EXPORTER Exporter: otlp, console ou none otlp
OTEL_METRICS_EXPORTER Exporter: otlp ou none otlp
OTEL_TRACING_ENABLED Override explícito para tracing true
OTEL_METRICS_ENABLED Override explícito para métricas true
OTEL_METRIC_EXPORT_INTERVAL Intervalo de export de métricas (ms) 60000
OTEL_AUTO_SHUTDOWN Registrar handlers SIGTERM/SIGINT para flush true
OTEL_ZERO_CODE Se false, o preload não inicializa true

Telemetria — Privacy

Variável Descrição Default
OTEL_PRIVACY_REDACT_DB_STATEMENT Redactar statements SQL nos spans true
OTEL_PRIVACY_HASH_USER_ID Fazer hash de user IDs nos spans true (via config)

Logs — Transporte

Todas as variáveis LOGS_* aceitam o prefixo LOKI_* como fallback.

Variável Descrição Default
LOGS_URL URL completa do Loki push endpoint
LOGS_TENANT Tenant ID (header X-Scope-OrgID)
LOGS_TOKEN Token de autenticação (Bearer)
LOGS_COMPRESSION gzip, brotli, snappy ou none gzip
LOGS_COMPRESSION_LEVEL Nível de compressão (0-11) 6
LOGS_COMPRESSION_THRESHOLD Tamanho mínimo para comprimir (bytes)
LOGS_USE_WORKERS Usar Worker Threads para processamento false
LOGS_MAX_WORKERS Número máximo de workers (1-64) 2
LOGS_CONNECTION_POOLING Pool de conexões HTTP (undici) false
LOGS_MAX_SOCKETS Conexões simultâneas no pool
LOGS_TIMEOUT Timeout HTTP (ms) 10000
LOGS_MAX_RETRIES Retentativas em caso de falha 3
LOGS_RETRY_DELAY Delay base entre retentativas (ms) 1000

Logs — Buffer e Filtro

Variável Descrição Default
LOGS_BUFFER_MAX_SIZE Tamanho máximo do buffer
LOGS_BUFFER_FLUSH_INTERVAL Intervalo de flush automático (ms)
LOGS_BUFFER_MAX_MEMORY_MB Limite de memória do buffer (MB)
LOGS_BUFFER_MAX_AGE Idade máxima de um log no buffer (ms)
LOGS_BUFFER_AUTO_FLUSH Habilitar auto-flush true
LOGS_FILTER_LEVELS Níveis permitidos (vírgula) debug,info,warn,error,fatal
LOGS_FILTER_SAMPLING_RATE Taxa de amostragem (0.0 a 1.0) 1.0
LOGS_FILTER_SANITIZE Sanitizar dados sensíveis false
LOGS_FILTER_MAX_MESSAGE_LENGTH Tamanho máximo da mensagem

Logs — Resiliência

Variável Descrição Default
LOGS_CIRCUIT_BREAKER_ENABLED Habilitar circuit breaker true
LOGS_CIRCUIT_BREAKER_FAILURE_THRESHOLD Falhas para abrir o circuito 5
LOGS_CIRCUIT_BREAKER_RESET_TIMEOUT Tempo para tentar fechar (ms) 30000
LOGS_CIRCUIT_BREAKER_HALF_OPEN_REQUESTS Requests de teste em half-open 3
LOGS_DLQ_ENABLED Habilitar Dead Letter Queue false
LOGS_DLQ_TYPE Tipo: memory ou file memory
LOGS_DLQ_MAX_SIZE Tamanho máximo da DLQ 1000
LOGS_DLQ_MAX_RETRIES Retentativas da DLQ 3
LOGS_DLQ_BASE_PATH Diretório para DLQ em arquivo ./.logs-dlq

Logs — Interceptação e Debug

Variável Descrição Default
LOGS_INTERCEPT_CONSOLE Capturar console.log/info/warn/error false
LOGS_PRESERVE_ORIGINAL_CONSOLE Manter output original ao interceptar true
LOGS_ENABLE_METRICS Métricas internas do logger false
LOGS_ENABLE_HEALTH_CHECK Health check do logger false
LOGS_DEBUG Logs de debug interno da lib false
LOGS_SILENT_ERRORS Suprimir erros internos false

Labels customizados

Qualquer variável com prefixo LOGS_LABEL_ vira um label no Loki:

LOGS_LABEL_SERVICE=minha-api
LOGS_LABEL_ENVIRONMENT=production
LOGS_LABEL_REGION=us-east-1
LOGS_LABEL_CLUSTER=prod

Guia por framework

Express

import { init, logger } from "elven-unified-observability-js";

async function bootstrap() {
  await init({
    serviceName: "minha-api-express",
    logging: {
      transport: {
        url: process.env.LOGS_URL,
        tenantId: process.env.LOGS_TENANT,
        authToken: process.env.LOGS_TOKEN,
      },
    },
    enableLogging: true,
    enableTracing: true,
    enableMetrics: true,
  });

  const express = (await import("express")).default;
  const app = express();

  app.use((req, res, next) => {
    const start = Date.now();
    res.on("finish", () => {
      logger.info("request", {
        method: req.method,
        path: req.path,
        status: res.statusCode,
        duration_ms: Date.now() - start,
      });
    });
    next();
  });

  app.get("/health", (req, res) => res.json({ status: "ok" }));

  app.listen(3000, () => logger.info("server started", { port: 3000 }));
}

bootstrap();

Ou via zero-code (sem alterar o código):

NODE_OPTIONS="--require elven-unified-observability-js/register" node app.js

Fastify

OTEL_INSTR_FASTIFY=true
NODE_OPTIONS="--require elven-unified-observability-js/register" node app.js

NestJS

OTEL_INSTR_NESTJS=true
OTEL_INSTR_EXPRESS=true
NODE_OPTIONS="--require elven-unified-observability-js/register" npx nest start

Next.js

NODE_OPTIONS="--require elven-unified-observability-js/register" npx next start

PM2

No ecosystem.config.js:

module.exports = {
  apps: [{
    name: "minha-api",
    script: "app.js",
    node_args: "--require elven-unified-observability-js/register",
    env: {
      LOGS_APP_NAME: "minha-api",
      LOGS_URL: "https://loki.elvenobservability.com/loki/api/v1/push",
      LOGS_TENANT: "seu-tenant-id",
      LOGS_TOKEN: "seu-api-token",
      OTEL_SERVICE_NAME: "minha-api",
      OTEL_EXPORTER_OTLP_ENDPOINT: "https://otel-collector.sua-infra.com:4318",
    },
  }],
};

Usando logger, tracer e metrics

Após a inicialização, os singletons ficam disponíveis em qualquer arquivo:

import { logger, tracer, metrics } from "elven-unified-observability-js";

Logger

// Níveis de log
logger.debug("detalhes técnicos", { key: "value" });
logger.info("evento importante", { userId: "123", action: "login" });
logger.warn("algo inesperado", { retryCount: 3 });
logger.error("falha no processamento", { error: "timeout", service: "payments" });
logger.fatal("erro crítico irrecuperável");

// Log genérico com nível
logger.log("info", "mensagem customizada", { custom: "data" });

// Tracking de eventos
logger.trackEvent("user.signup", { plan: "pro", source: "landing" });

// Context — adiciona metadata a todos os logs no bloco
logger.withContext({ requestId: "abc-123", userId: "456" }, () => {
  logger.info("processando request");  // inclui requestId e userId
  logger.info("request finalizado");
});

// Context async
await logger.withContextAsync({ requestId: "abc-123" }, async () => {
  logger.info("processando async");
  await someAsyncWork();
  logger.info("async finalizado");
});

// Flush manual
await logger.flush();

// Health e métricas do logger
const health = logger.getHealth();
const loggerMetrics = logger.getMetrics();

Tracer

// Span com callback (gerencia lifecycle automaticamente)
await tracer.startActiveSpan("process-order", async (span) => {
  span.setAttribute("order.id", "ORD-789");
  span.setAttribute("order.total", 150.00);

  // ... lógica ...

  span.end();
});

// Span com try/catch
await tracer.startActiveSpan("fetch-data", async (span) => {
  try {
    const data = await fetch("https://api.example.com/data");
    span.setAttribute("response.status", data.status);
    return data.json();
  } catch (err) {
    span.recordException(err as Error);
    throw err;
  } finally {
    span.end();
  }
});

Metrics

const meter = metrics.getMeter("minha-api");

// Contador
const requestCounter = meter.createCounter("requests_total");
requestCounter.add(1, { method: "GET", route: "/api/users" });

// Histograma
const latencyHistogram = meter.createHistogram("request_duration_ms");
latencyHistogram.record(145.2, { route: "/api/users" });

// UpDownCounter (para valores que sobem e descem)
const activeConnections = meter.createUpDownCounter("active_connections");
activeConnections.add(1);   // nova conexão
activeConnections.add(-1);  // conexão fechada

Instrumentações automáticas

A lib instrumenta automaticamente as bibliotecas instaladas no seu projeto. Controle quais ficam ativas:

Toggles por variável de ambiente

Variável Biblioteca Default
OTEL_INSTR_HTTP http/https nativo true
OTEL_INSTR_EXPRESS Express true
OTEL_INSTR_FASTIFY Fastify true
OTEL_INSTR_GRAPHQL GraphQL true
OTEL_INSTR_PG PostgreSQL (pg) true
OTEL_INSTR_MYSQL MySQL (mysql2) true
OTEL_INSTR_MONGODB MongoDB true
OTEL_INSTR_MONGOOSE Mongoose true
OTEL_INSTR_REDIS Redis + ioredis true
OTEL_INSTR_KNEX Knex.js true
OTEL_INSTR_GENERIC_POOL generic-pool true
OTEL_INSTR_NESTJS NestJS true
OTEL_INSTR_KOA Koa true
OTEL_INSTR_PINO Pino true
OTEL_INSTR_WINSTON Winston true
OTEL_INSTR_DNS DNS false
OTEL_INSTR_NET Net (TCP) false

Valores aceitos: true / 1 / yes / on para habilitar, false / 0 / no / off para desabilitar.

Exemplo: habilitar apenas o necessário

# API Express com PostgreSQL e Redis
OTEL_INSTR_EXPRESS=true
OTEL_INSTR_PG=true
OTEL_INSTR_REDIS=true
OTEL_INSTR_HTTP=true

Via config programático

await init({
  // ...
  telemetry: {
    instrumentations: {
      express: true,
      pg: true,
      redis: true,
      mongodb: false,
      graphql: false,
    },
  },
});

Configuração avançada

Config completo programático

import { init } from "elven-unified-observability-js";

await init({
  serviceName: "payments-service",
  version: "2.1.0",
  environment: "production",

  logging: {
    transport: {
      url: "https://loki.elvenobservability.com/loki/api/v1/push",
      tenantId: "meu-tenant",
      authToken: "meu-token",
      compression: "gzip",
      compressionLevel: 6,
      useWorkers: true,
      maxWorkers: 4,
      enableConnectionPooling: true,
      maxSockets: 50,
      timeout: 10000,
      maxRetries: 3,
      retryDelay: 1000,
    },
    labels: {
      team: "payments",
      cluster: "prod-us",
    },
    buffer: {
      maxSize: 500,
      flushInterval: 3000,
      maxMemoryMB: 256,
    },
    filter: {
      levels: ["info", "warn", "error", "fatal"],
      samplingRate: 1.0,
      sanitize: true,
      maxMessageLength: 8192,
    },
    circuitBreaker: {
      enabled: true,
      failureThreshold: 10,
      resetTimeout: 30000,
    },
    deadLetterQueue: {
      enabled: true,
      type: "file",
      maxSize: 5000,
      basePath: "./.logs-dlq",
    },
    interceptConsole: true,
    preserveOriginalConsole: true,
  },

  telemetry: {
    exporters: {
      protocol: "http/protobuf",
      compression: "gzip",
    },
    tracing: {
      enabled: true,
      sampling: { ratio: 1.0 },
    },
    metrics: {
      enabled: true,
      exportIntervalMillis: 15000,
    },
    privacy: {
      redactDbStatement: true,
      hashUserId: true,
    },
    instrumentations: {
      express: true,
      pg: true,
      redis: true,
      mongodb: true,
      graphql: false,
    },
  },

  enableLogging: true,
  enableMetrics: true,
  enableTracing: true,
});

Integrações com bibliotecas de logging

Winston

import winston from "winston";
import { WinstonTransport } from "elven-logs-interceptor";

const winstonLogger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new WinstonTransport(),  // envia para Loki via pipeline Elven
  ],
});

winstonLogger.info("log via winston", { custom: "metadata" });

Morgan (HTTP access logs)

import express from "express";
import morgan from "morgan";
import { MorganAdapter } from "elven-logs-interceptor";
import { logger } from "elven-unified-observability-js";

const app = express();

app.use(morgan("combined", {
  stream: MorganAdapter.createStream(logger),
}));

Boas práticas

Naming convention para serviços

Use nomes descritivos e consistentes:

{dominio}-{componente}

Exemplos: payments-api, orders-worker, notifications-scheduler, auth-service.

Labels no Loki

Use labels para dimensões de baixa cardinalidade. Não use para valores de alta cardinalidade:

# Bom: baixa cardinalidade
LOGS_LABEL_SERVICE=payments-api
LOGS_LABEL_ENVIRONMENT=production
LOGS_LABEL_CLUSTER=prod-us

# Ruim: alta cardinalidade — use como metadata no log
# LOGS_LABEL_USER_ID=...
# LOGS_LABEL_REQUEST_ID=...

Sampling em produção

Para serviços com alto throughput:

await init({
  telemetry: {
    tracing: {
      sampling: { ratio: 0.1 },  // 10% dos traces
    },
  },
  // ...
});

Inicialização antes dos imports

Para que o OTel consiga instrumentar corretamente, a inicialização deve acontecer antes de importar os frameworks. O modo zero-code (--require) garante isso automaticamente. No modo programático:

// ✅ Correto: init antes do import
await init({ ... });
const express = (await import("express")).default;

// ❌ Errado: import antes do init
import express from "express";  // OTel não consegue instrumentar
await init({ ... });

Worker Threads para alta carga

Para serviços com alto volume de logs, habilite workers para não bloquear a event loop:

LOGS_USE_WORKERS=true
LOGS_MAX_WORKERS=4

Connection Pooling

Para envio eficiente ao Loki:

LOGS_CONNECTION_POOLING=true
LOGS_MAX_SOCKETS=50

Deploy com Docker

Dockerfile

Se a lib já está no package.json da aplicação:

FROM node:20-slim

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

CMD ["node", "--require", "elven-unified-observability-js/register", "app.js"]

Se você quer adicionar a instrumentação direto no Dockerfile sem alterar o package.json da aplicação (útil para instrumentar apps existentes sem mexer no código):

FROM node:20-slim

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

# Instalar a lib de observabilidade separadamente
RUN npm install elven-unified-observability-js

COPY . .

CMD ["node", "--require", "elven-unified-observability-js/register", "app.js"]

docker run

docker build -t minha-api .

docker run -p 3000:3000 <br>
  -e LOGS_APP_NAME=minha-api <br>
  -e LOGS_APP_VERSION=1.0.0 <br>
  -e NODE_ENV=production <br>
  -e OTEL_SERVICE_NAME=minha-api <br>
  -e OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.sua-infra.com:4318 <br>
  -e OTEL_TRACES_EXPORTER=otlp <br>
  -e OTEL_METRICS_EXPORTER=otlp <br>
  -e LOGS_URL=https://loki.elvenobservability.com/loki/api/v1/push <br>
  -e LOGS_TENANT=seu-tenant-id <br>
  -e LOGS_TOKEN=seu-api-token <br>
  -e LOGS_COMPRESSION=gzip <br>
  -e ENABLE_LOGGING=true <br>
  -e ENABLE_TRACING=true <br>
  -e ENABLE_METRICS=true <br>
  minha-api

docker-compose.yml

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      # Identidade
      LOGS_APP_NAME: minha-api
      LOGS_APP_VERSION: "1.0.0"
      NODE_ENV: production
      OTEL_SERVICE_NAME: minha-api

      # Telemetria
      OTEL_EXPORTER_OTLP_ENDPOINT: https://otel-collector.sua-infra.com:4318
      OTEL_TRACES_EXPORTER: otlp
      OTEL_METRICS_EXPORTER: otlp
      OTEL_METRIC_EXPORT_INTERVAL: "15000"
      OTEL_INSTR_EXPRESS: "true"
      OTEL_PRIVACY_REDACT_DB_STATEMENT: "true"

      # Logs
      LOGS_URL: https://loki.elvenobservability.com/loki/api/v1/push
      LOGS_TENANT: seu-tenant-id
      LOGS_TOKEN: seu-api-token
      LOGS_COMPRESSION: gzip
      LOGS_INTERCEPT_CONSOLE: "true"

      # Controle
      ENABLE_LOGGING: "true"
      ENABLE_TRACING: "true"
      ENABLE_METRICS: "true"

Dockerfile multi-stage (produção)

FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
# Se a lib não está no package.json, adicione aqui:
RUN npm install elven-unified-observability-js
COPY --from=builder /app/dist ./dist

RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

EXPOSE 3000
CMD ["node", "--require", "elven-unified-observability-js/register", "dist/app.js"]

Kubernetes — Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: minha-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: minha-api
  template:
    metadata:
      labels:
        app: minha-api
    spec:
      containers:
        - name: api
          image: minha-api:1.0.0
          ports:
            - containerPort: 3000
          env:
            - name: LOGS_APP_NAME
              value: minha-api
            - name: NODE_ENV
              value: production
            - name: OTEL_SERVICE_NAME
              value: minha-api
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: https://otel-collector.sua-infra.com:4318
            - name: OTEL_TRACES_EXPORTER
              value: otlp
            - name: OTEL_METRICS_EXPORTER
              value: otlp
            - name: LOGS_URL
              value: https://loki.elvenobservability.com/loki/api/v1/push
            - name: LOGS_TENANT
              valueFrom:
                secretKeyRef:
                  name: elven-credentials
                  key: tenant-id
            - name: LOGS_TOKEN
              valueFrom:
                secretKeyRef:
                  name: elven-credentials
                  key: api-token
            - name: LOGS_COMPRESSION
              value: gzip
            - name: ENABLE_LOGGING
              value: "true"
            - name: ENABLE_TRACING
              value: "true"
            - name: ENABLE_METRICS
              value: "true"
            - name: LOGS_LABEL_CLUSTER
              value: prod-us

Troubleshooting

A aplicação não inicia / crash no bootstrap

Sintoma: Erro no require ou na inicialização.

Verificações:

  1. Confirme a versão do Node.js (node --version): requer 18+
  2. Confirme que o pacote está instalado: npm ls elven-unified-observability-js
  3. Se usar init programático, verifique que init() é chamado com await
node -e "require('elven-unified-observability-js'); console.log('OK')"

Logs não aparecem no Loki

Sintoma: A aplicação roda normalmente, mas nenhum log aparece.

Verificações:

  1. Confirme que LOGS_URL está correto e inclui o path completo: https://loki.elvenobservability.com/loki/api/v1/push
  2. Confirme que LOGS_TENANT e LOGS_TOKEN estão preenchidos
  3. Confirme que ENABLE_LOGGING=true
  4. Habilite debug: LOGS_DEBUG=true e verifique o stderr
LOGS_DEBUG=true node --require elven-unified-observability-js/register app.js

Traces não aparecem

Sintoma: Logs funcionam, mas traces não aparecem.

Verificações:

  1. Confirme que OTEL_EXPORTER_OTLP_ENDPOINT está definido
  2. Confirme que OTEL_TRACES_EXPORTER=otlp
  3. Confirme que ENABLE_TRACING=true
  4. No modo programático, verifique que init() roda antes de importar o framework

Auto-instrumentação não funciona

Sintoma: Requests HTTP não geram spans automaticamente.

Verificações:

  1. A auto-instrumentação requer que o OTel seja carregado antes dos módulos. Use --require ou faça init() antes de qualquer import/require
  2. Confirme que a instrumentação está ativa: OTEL_INSTR_EXPRESS=true
  3. Confirme que a biblioteca está instalada: npm ls express

Erro de autenticação (401/403) no Loki

Sintoma: LOGS_DEBUG=true mostra erro de autenticação.

Verificações:

  1. Confirme LOGS_TOKEN no painel da Elven
  2. Confirme LOGS_TENANT
  3. Verifique se o token não expirou

Console interceptado não mostra output

Sintoma: console.log não aparece no terminal.

Causa: LOGS_INTERCEPT_CONSOLE=true com LOGS_PRESERVE_ORIGINAL_CONSOLE=false.

Solução: Defina LOGS_PRESERVE_ORIGINAL_CONSOLE=true para manter o output no terminal além de enviar para o Loki.

Memory leak / alto consumo de memória

Sintoma: Uso de memória cresce continuamente.

Verificações:

  1. Limite o buffer: LOGS_BUFFER_MAX_SIZE=500 e LOGS_BUFFER_MAX_MEMORY_MB=256
  2. Habilite circuit breaker: LOGS_CIRCUIT_BREAKER_ENABLED=true
  3. Se o Loki estiver inacessível, logs acumulam. A DLQ em arquivo evita crescimento em memória:
LOGS_DLQ_ENABLED=true
LOGS_DLQ_TYPE=file
LOGS_DLQ_BASE_PATH=./.logs-dlq

FAQ

Preciso instalar os 3 pacotes separadamente? Não. Instale apenas elven-unified-observability-js — ele puxa as duas libs automaticamente.

Funciona com TypeScript? Sim. O pacote inclui type definitions (.d.ts). Funciona com ts-node, tsx, tsc, etc.

Funciona com ESM e CommonJS? Sim. O pacote exporta CommonJS (dist/index.js) e é compatível com ESM via dynamic import. O --require funciona em ambos os modos.

Posso usar apenas logs sem traces? Sim. Defina ENABLE_TRACING=false e ENABLE_METRICS=false.

Posso usar apenas traces sem logs? Sim. Defina ENABLE_LOGGING=false.

Qual a diferença entre LOGS_URL no JS e LOKI_URL na Lambda layer? Na lib JS, LOGS_URL deve ser a URL completa incluindo o path (/loki/api/v1/push). Na Lambda layer (Go), LOKI_URL é apenas a URL base, pois a extension appenda o path automaticamente.

Posso usar com Winston / Morgan? Sim. A lib fornece WinstonTransport e MorganAdapter que redirecionam logs para a pipeline do Loki. Veja a seção Integrações com bibliotecas de logging.

Qual versão mínima do Node.js? Node.js 18 ou superior.

O init() é idempotente? Sim. Chamar init() mais de uma vez retorna os mesmos singletons sem reinicializar.

Como funciona o circuit breaker? Se o Loki retornar erros consecutivos, o circuito "abre" e logs são descartados (ou vão para a DLQ). Após o timeout de reset, tenta fechar gradualmente com requests de teste (half-open).

Posso usar com PM2? Sim. Use node_args: "--require elven-unified-observability-js/register" no ecosystem.config.js. Veja a seção PM2.

E com serverless (Lambda)? Para Lambda, use as layers dedicadas em vez desta lib. As layers são otimizadas para o lifecycle da Lambda.