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:
- Confirme a versão do Node.js (
node --version): requer 18+ - Confirme que o pacote está instalado:
npm ls elven-unified-observability-js - Se usar init programático, verifique que
init()é chamado comawait
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:
- Confirme que
LOGS_URLestá correto e inclui o path completo:https://loki.elvenobservability.com/loki/api/v1/push - Confirme que
LOGS_TENANTeLOGS_TOKENestão preenchidos - Confirme que
ENABLE_LOGGING=true - Habilite debug:
LOGS_DEBUG=truee 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:
- Confirme que
OTEL_EXPORTER_OTLP_ENDPOINTestá definido - Confirme que
OTEL_TRACES_EXPORTER=otlp - Confirme que
ENABLE_TRACING=true - 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:
- A auto-instrumentação requer que o OTel seja carregado antes dos módulos. Use
--requireou façainit()antes de qualquerimport/require - Confirme que a instrumentação está ativa:
OTEL_INSTR_EXPRESS=true - 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:
- Confirme
LOGS_TOKENno painel da Elven - Confirme
LOGS_TENANT - 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:
- Limite o buffer:
LOGS_BUFFER_MAX_SIZE=500eLOGS_BUFFER_MAX_MEMORY_MB=256 - Habilite circuit breaker:
LOGS_CIRCUIT_BREAKER_ENABLED=true - 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.