Logs Interceptor JS

Elven Observability Official Logger

Um coletor de logs, eventos e exceções de alta performance para apps Node.js que envia tudo ao Grafana Loki. Oferece interceptação automática de console, suporte a labels dinâmicas, envio assíncrono com buffer otimizado e integração instantânea com o Elven Stack.

Features

Alta performance com buffer assíncrono, envio em lote e compressão gzip.

Captura automática de:

  • console.log, console.error, console.warn, etc
  • Exceções não tratadas e promises rejeitadas
  • Eventos personalizados via trackEvent

Suporte a labels fixas e dinâmicas (com trace/span id via OpenTelemetry).

Buffer inteligente com envio em lote configurável.

Resiliente a falhas de rede com retry automático.

Compatível com apps simples ou complexos (web servers, workers, CLI).

Garantia de envio no encerramento do processo.

Compressão gzip para reduzir uso de banda.

Métricas em tempo real e health check integrado.

Instalação

npm install logs-interceptor

Uso Básico

import { init, logger } from "logs-interceptor";

init({
  transport: {
    url: "<https://loki.elvenobservability.com/loki/api/v1/push>",
    tenantId: "seu-tenant", // Seu Tenant ID
    authToken: "seu-token", // Seu JWT
    compression: true, // Gzip habilitado
  },
  appName: "my-js-app",
  environment: "production",
  interceptConsole: true,
  labels: { env: "production" },
  dynamicLabels: {
    hostname: () => require('os').hostname(),
    memory: () => Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + 'MB'
  },
  buffer: {
    maxSize: 100,
    flushInterval: 5000,
  }
});

console.log("Isso será enviado ao Loki!");
logger.trackEvent("usuario_cadastrado", { user_id: 123 });
throw new Error("Erro de teste!"); // Capturado automaticamente

Opções de Configuração

Transport

Parâmetro Tipo Descrição Padrão
url string Endpoint do Loki (Push API)
tenantId string Usado no header X-Scope-OrgID
authToken string Token JWT para autenticação undefined
timeout number Timeout HTTP em ms 5000
maxRetries number Tentativas em caso de falha 3
compression boolean Habilita compressão gzip true

Aplicação

Parâmetro Tipo Descrição Padrão
appName string Nome da aplicação (label fixa)
version string Versão da aplicação “1.0.0”
environment string Ambiente (prod, dev, staging) “production”
labels object Labels fixas por log {}
dynamicLabels object Labels geradas dinamicamente {}

Buffer & Performance

Parâmetro Tipo Descrição Padrão
buffer.maxSize number Logs no buffer antes de forçar envio 100
buffer.flushInterval number Tempo máximo antes do envio (ms) 5000
buffer.autoFlush boolean Flush automático habilitado true
interceptConsole boolean Captura console.* automaticamente false
enableMetrics boolean Coleta métricas de performance true

Filtering & Sampling

Parâmetro Tipo Descrição Padrão
filter.levels array Níveis de log permitidos [‘debug’,’info’,’warn’,’error’,’fatal’]
filter.samplingRate number Taxa de amostragem (0.0-1.0) 1.0
filter.maxMessageLength number Tamanho máximo da mensagem 8192

Exemplo com Express.js

import express from "express";
import { init, logger } from "logs-interceptor";

init({
  transport: {
    url: "<https://loki.elvenobservability.com/loki/api/v1/push>",
    tenantId: "seu-tenantId",
    authToken: "seu-token",
    compression: true,
  },
  appName: "express-app",
  environment: "production",
  interceptConsole: true,
  labels: {
    service: "api",
    env: "production"
  },
  dynamicLabels: {
    hostname: () => require('os').hostname(),
    uptime: () => Math.round(process.uptime()) + 's'
  },
});

const app = express();

app.get("/ping", (req, res) => {
  console.log("ping chamado"); // Interceptado automaticamente
  logger.trackEvent("api_ping", { ip: req.ip });
  res.send({ message: "pong" });
});

app.get("/erro", () => {
  throw new Error("Erro proposital"); // Capturado automaticamente
});

app.listen(3000, () => {
  console.log("Servidor iniciado na porta 3000");
});

Auto-inicialização com NODE_OPTIONS

Para apps existentes sem modificação de código:

NODE_OPTIONS="--require logs-interceptor/preload" \<br>
LOGS_INTERCEPTOR_URL="<https://loki.elvenobservability.com/loki/api/v1/push>" \<br>
LOGS_INTERCEPTOR_TENANT_ID="seu-tenant" \<br>
LOGS_INTERCEPTOR_APP_NAME="minha-api" \<br>
LOGS_INTERCEPTOR_AUTH_TOKEN="seu-token" \<br>
LOGS_INTERCEPTOR_ENVIRONMENT="production" \<br>
node app.js

Métricas & Monitoramento

import { logger } from "logs-interceptor";

// Métricas em tempo real
const metrics = logger.getMetrics();
console.log({
  logsProcessed: metrics.logsProcessed,
  logsDropped: metrics.logsDropped,
  flushCount: metrics.flushCount,
  avgFlushTime: metrics.avgFlushTime,
  errorCount: metrics.errorCount
});

// Health check
const health = logger.getHealth();
console.log({
  healthy: health.healthy,
  uptime: health.uptime,
  bufferUtilization: health.bufferUtilization
});

Boas Práticas

Situação Recomendações
Scripts curtos (CLI, cron) Use await logger.flush() antes do exit.
Apps web ou workers Configuração padrão já garante envio em background.
Alta concorrência Use maxSize: 500+ e flushInterval: 10000.
Ambientes com OpenTelemetry Labels trace_id e span_id são capturados automaticamente.
Produção Habilite compression: true e samplingRate < 1.0 se necessário.

Troubleshooting

Problema Causa comum Solução
Logs não aparecem tenantId, authToken ou URL errados Verifique configuração e use debug: true
Logs incompletos Processo encerrando rápido Use await logger.flush() antes do exit
HTTP 400 Payload malformado Verifique se Loki está rodando na URL
Performance ruim Buffer muito pequeno Aumente maxSize e flushInterval

Flush Manual

import { logger } from "logs-interceptor";

// Forçar envio imediato
await logger.flush();

// Exemplo com graceful shutdown
process.on('SIGTERM', async () => {
  console.log('Shutting down gracefully...');
  await logger.flush();
  process.exit(0);
});

Eventos Customizados

import { logger } from "logs-interceptor";

logger.trackEvent("usuario_ativo", {
  user_id: "abc123",
  source: "mobile",
  action: "login"
});

logger.trackEvent("compra_realizada", {
  order_id: "12345",
  value: 99.90,
  payment_method: "credit_card"
});

No Loki aparecerá como:

[EVENT] usuario_ativo {"user_id": "abc123", "source": "mobile", "action": "login"}

Formato dos Logs no Loki

Cada log enviado inclui:

  • Timestamp com nanossegundos
  • Labels fixas e dinâmicas (ex: app, env, trace_id, span_id)
  • Prefixo do tipo de log ([INFO], [ERROR], [EVENT], etc)
  • Conteúdo serializado automaticamente para objetos e erros

Exemplo de payload enviado:

{
  "streams": [
    {
      "stream": {
        "app": "minha-api",
        "environment": "production",
        "level": "info",
        "hostname": "api-01",
        "trace_id": "abc123def456"
      },
      "values": [
        ["1640995200000000000", "[INFO] Usuario logado com sucesso {\\"userId\\": 123}"]
      ]
    }
  ]
}

Integração com OpenTelemetry

Este logger se integra automaticamente com o OpenTelemetry, capturando os IDs de trace e span ativos no contexto atual.

Isso permite correlação perfeita entre logs e traces no Grafana Tempo, permitindo investigações completas ponta a ponta.

O que já está incluso:

  • trace_id e span_id são adicionados automaticamente como labels dinâmicas
  • Nenhuma configuração extra é necessária, desde que o app já use o OpenTelemetry
  • Compatível com qualquer instrumentação OpenTelemetry existente

Exemplo com traces

import { context, trace } from "@opentelemetry/api";
import { init, logger } from "logs-interceptor";

// Inicialize o OpenTelemetry antes:
const tracer = trace.getTracer("minha-lib");

init({
  transport: {
    url: "<https://loki.elvenobservability.com/loki/api/v1/push>",
    tenantId: "elven",
    authToken: "seu-token"
  },
  appName: "meu-app",
  labels: { env: "prod" },
});

tracer.startActiveSpan("processarPedido", (span) => {
  console.log("Processando pedido");
  logger.info("Pedido iniciado", { orderId: 12345 });
  span.end();
});

No Loki:

Cada log conterá automaticamente:

{
  "trace_id": "abcdef123456789",
  "span_id": "12345678",
  "app": "meu-app",
  "level": "info"
}

Você poderá fazer queries no Loki filtrando por trace_id e, com isso, visualizar os logs do mesmo trace mostrado no Grafana Tempo (ou trace view da Elven Platform).

Docker & Kubernetes

Docker Compose

version: '3.8'
services:
  app:
    build: .
    environment:
      - NODE_OPTIONS=--require logs-interceptor/preload
      - LOGS_INTERCEPTOR_URL=https://loki.elvenobservability.com/loki/api/v1/push
      - LOGS_INTERCEPTOR_TENANT_ID=meu-tenant
      - LOGS_INTERCEPTOR_AUTH_TOKEN=meu-token
      - LOGS_INTERCEPTOR_APP_NAME=minha-api
      - LOGS_INTERCEPTOR_ENVIRONMENT=production

Kubernetes

env:
- name: NODE_OPTIONS
  value: "--require logs-interceptor/preload"
- name: LOGS_INTERCEPTOR_URL
  value: "<https://loki.elvenobservability.com/loki/api/v1/push>"
- name: LOGS_INTERCEPTOR_TENANT_ID
  valueFrom:
    secretKeyRef:
      name: elven-credentials
      key: tenant-id
- name: LOGS_INTERCEPTOR_AUTH_TOKEN
  valueFrom:
    secretKeyRef:
      name: elven-credentials
      key: auth-token

Licença

Este projeto é licenciado sob a MIT License.

Contribuindo

  1. Fork este repositório
  2. Crie uma branch com sua melhoria
  3. Envie um Pull Request

Toda ajuda é bem-vinda para tornar o logger ainda mais poderoso!

Feito com ❤️ pela equipe Elven Observability