Cloud e plataforma

OpenTelemetry em Produção: Estratégias de Observabilidade Unificada

Observabilidade fragmentada (logs, metrics, tracing separados) dificulta debugging. OpenTelemetry unifica coleta, padroniza dados e facilita integração com múltiplas ferramentas.

16/03/20268 min de leituraCloud
OpenTelemetry em Produção: Estratégias de Observabilidade Unificada

Resumo executivo

Observabilidade fragmentada (logs, metrics, tracing separados) dificulta debugging. OpenTelemetry unifica coleta, padroniza dados e facilita integração com múltiplas ferramentas.

Ultima atualizacao: 16/03/2026

Resumo executivo

A abordagem tradicional de observabilidade—implementar logs em um lugar, metrics em outro, tracing em outro—cria fragmentação de dados, inconsistências de instrumentação e complexidade operacional. Debugging uma requisição que falha exige acessar múltiplos dashboards diferentes para ver logs, métricas e traces relacionados.

OpenTelemetry (OTel) resolve essa fragmentação fornecendo uma especificação e SDKs unificados para coleta de logs, metrics e traces. Em 2026, OpenTelemetry se consolidou como padrão de facto para observabilidade, com suporte nativo em todos os principais provedores de cloud e APM vendors.

Organizações que implementam OpenTelemetry corretamente reduzem esforço de instrumentação, eliminam fragmentação de dados e facilitam migração entre ferramentas de observabilidade sem mudar código de aplicação.

Problema da observabilidade fragmentada

Sintomas de implementação separada

1. Contexto perdido entre signals

typescript// ❌ Fragmentado: cada signal tem contexto diferente
// logging.ts
logger.info('User created', { userId: '123' });

// metrics.ts
metrics.increment('user.created', { userId: '123' });

// tracing.ts
tracer.startSpan('createUser', { userId: '123' });

// ✅ Unificado: contexto propagado automaticamente
import { trace, metrics, logger } from '@opentelemetry/api';

const context = trace.setSpanContext({
  'user.id': '123',
  'user.role': 'admin'
});

logger.info('User created', context);
metrics.increment('user.created', context);
tracer.startSpan('createUser', context);

2. Inconsistência de atributos

typescript// ❌ Diferentes nomes para o mesmo conceito
logger.info({ 'user_id': '123' });
metrics.record('user_created', { 'userId': '123' });
tracer.recordEvent('UserCreate', { 'UserID': '123' });

// ✅ Padronizado: semantic attributes
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';

logger.info('User created', {
  [SemanticAttributes.USER_ID]: '123',
  [SemanticAttributes.USER_ROLE]: 'admin'
});
metrics.record('user.created', {
  [SemanticAttributes.USER_ID]: '123',
  [SemanticAttributes.USER_ROLE]: 'admin'
});

3. Dificuldade de migração

Quando você muda de um APM vendor para outro, precisa reimplementar toda instrumentação.

typescript// ❌ Vendor lock-in
import { DataDogLogger } from '@datadog/browser-logs';
import { DataDogMetrics } from '@datadog/browser-metrics';
import { DataDogTracer } from '@datadog/browser-tracing';

// Se mudar para New Relic, tudo muda
import { NewRelicLogger } from 'newrelic/browser-logs';
import { NewRelicMetrics } from 'newrelic/browser-metrics';

// ✅ Vendor-agnostic: apenas trocar o exporter
import { trace, metrics, logger } from '@opentelemetry/api';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';

// Mudar de Jaeger para Prometheus = apenas mudar exporter

Arquitetura de OpenTelemetry

Camadas da especificação OTel

┌─────────────────────────────────────────────────────────────────┐
│                  OPEN TELEMETRY SPECIFICATION                 │
├─────────────────────────────────────────────────────────────────┤
│                                                           │
│  API Layer                                                │
│  ├── Tracing API            (spans, context, links)      │
│  ├── Metrics API             (counter, gauge, histogram)      │
│  ├── Logs API                (structured logs)                │
│  └── Baggage API             (context propagation)          │
│                                                           │
│  SDK Layer                                                 │
│  ├── Instrumentation Libraries (auto-instrumentation)        │
│  ├── Language SDKs           (JavaScript, Python, Go)     │
│  └── Semantic Conventions     (standard attribute names)    │
│                                                           │
│  Collector Layer                                           │
│  ├── OTLP Receiver          (receives telemetry data)      │
│  ├── Processors           (batch, transform, filter)      │
│  └── Exporters            (Jaeger, Prometheus, vendors)    │
│                                                           │
└─────────────────────────────────────────────────────────────────┘

Componentes essenciais

1. Tracer Provider

typescript// src/tracing.ts
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { trace } from '@opentelemetry/api';

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'my-app',
  [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV,
});

const provider = new NodeTracerProvider({
  resource,
  spanProcessors: [
    new BatchSpanProcessor(
      new OTLPTraceExporter({
        url: 'http://otel-collector:4317/v1/traces',
      })
    ),
  ],
});

provider.register();

export { trace };

2. Metrics Provider

typescript// src/metrics.ts
import { MeterProvider } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics-base';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { metrics } from '@opentelemetry/api';

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'my-app',
  [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
});

const provider = new MeterProvider({
  resource,
  metricReader: new PeriodicExportingMetricReader(
    new OTLPMetricExporter({
      url: 'http://otel-collector:4317/v1/metrics',
    })
  ),
});

provider.register();

export const meter = metrics.getMeter('my-app');

3. Logger Provider

typescript// src/logging.ts
import { LoggerProvider } from '@opentelemetry/sdk-logs';
import { Resource } from '@opentelemetry/resources';
import { SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs-base';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { logs } from '@opentelemetry/api';
import { SeverityNumber } from '@opentelemetry/api-logs';

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'my-app',
  [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
});

const provider = new LoggerProvider({
  resource,
  logRecordProcessors: [
    new SimpleLogRecordProcessor(
      new OTLPLogExporter({
        url: 'http://otel-collector:4317/v1/logs',
      })
    ),
  ],
});

provider.register();

export const logger = logs.getLogger('my-app');

Instrumentação manual

Instrumentando handlers HTTP

typescript// src/instrumentation/http.ts
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { HTTP } from '@opentelemetry/semantic-conventions';

export function instrumentHttpHandler(handler: RequestHandler) {
  return async (req, res, next) => {
    // Criar span para a requisição HTTP
    const tracer = trace.getTracer('http');
    const span = tracer.startSpan(`${req.method} ${req.path}`, {
      kind: 'server',
      attributes: {
        [SemanticAttributes.HTTP_METHOD]: req.method,
        [SemanticAttributes.HTTP_URL]: req.url,
        [SemanticAttributes.HTTP_ROUTE]: req.path,
      },
    });

    try {
      const result = await handler(req, res, next);

      // Adicionar atributos de resposta
      span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, res.statusCode);
      span.setStatus({
        code: SpanStatusCode.OK,
      });

      return result;
    } catch (error) {
      // Registrar erro no span
      span.recordException(error as Error);
      span.setStatus({
        code: SpanStatusCode.ERROR,
      });
      throw error;
    } finally {
      // Finalizar span
      span.end();
    }
  };
}

Instrumentando operações de banco de dados

typescript// src/instrumentation/database.ts
import { trace, SpanKind } from '@opentelemetry/api';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';

export function instrumentDatabase<T>(
  operation: string,
  dbSystem: string,
  fn: () => Promise<T>
): Promise<T> {
  const tracer = trace.getTracer('database');

  return tracer.startActiveSpan(operation, {
    kind: SpanKind.CLIENT,
    attributes: {
      [SemanticAttributes.DB_SYSTEM]: dbSystem,
      [SemanticAttributes.DB_OPERATION]: operation,
    },
  }, async (span) => {
    try {
      const startTime = Date.now();
      const result = await fn();
      const duration = Date.now() - startTime;

      // Registrar duração como métrica
      meter
        .createHistogram('db.operation.duration', {
          unit: 'ms',
          description: 'Duration of database operations',
        })
        .record(duration, {
          [SemanticAttributes.DB_SYSTEM]: dbSystem,
          [SemanticAttributes.DB_OPERATION]: operation,
        });

      span.setAttribute(SemanticAttributes.DB_STATEMENT, 'SUCCESS');
      return result;
    } catch (error) {
      span.recordException(error as Error);
      span.setAttribute(SemanticAttributes.DB_STATEMENT, 'ERROR');
      throw error;
    } finally {
      span.end();
    }
  });
}

Instrumentando com context propagation

typescript// src/api/user.ts
import { trace, context } from '@opentelemetry/api';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { instrumentDatabase } from '../instrumentation/database';

export async function getUser(userId: string) {
  // Contexto já existe da requisição HTTP
  const tracer = trace.getTracer('api');
  const span = tracer.startSpan('getUser');

  try {
    // Propagar contexto para database
    const user = await instrumentDatabase(
      'SELECT',
      'postgresql',
      () => db.users.findUnique({ where: { id: userId } })
    );

    span.setAttribute(SemanticAttributes.DB_USER, userId);
    span.setAttribute('success', 'true');

    return user;
  } catch (error) {
    span.recordException(error as Error);
    throw error;
  } finally {
    span.end();
  }
}

Semantic conventions

Atributos padrão de serviço

typescriptimport { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';

const serviceAttributes = {
  // Identificação do serviço
  [SemanticResourceAttributes.SERVICE_NAME]: 'api-gateway',
  [SemanticResourceAttributes.SERVICE_VERSION]: '2.1.0',
  [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: 'gateway-1',

  // Ambiente de deploy
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV,
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT_NAME]: 'production',
  [SemanticResourceAttributes.HOST_NAME]: 'api-gateway-prod',

  // Identificação do processo
  [SemanticResourceAttributes.PROCESS_PID]: process.pid,
  [SemanticResourceAttributes.PROCESS_EXECUTABLE_PATH]: process.execPath,
  [SemanticResourceAttributes.PROCESS_COMMAND]: process.argv[0],
  [SemanticResourceAttributes.PROCESS_COMMAND_ARGS]: JSON.stringify(process.argv.slice(1)),
};

Atributos padrão de HTTP

typescriptimport { SemanticAttributes } from '@opentelemetry/semantic-conventions';

const httpAttributes = {
  // Requisição
  [SemanticAttributes.HTTP_METHOD]: 'POST',
  [SemanticAttributes.HTTP_URL]: 'https://api.example.com/users',
  [SemanticAttributes.HTTP_TARGET]: '/api/v1/users',
  [SemanticAttributes.HTTP_SCHEME]: 'https',
  [SemanticAttributes.HTTP_HOST]: 'api.example.com',

  // Resposta
  [SemanticAttributes.HTTP_STATUS_CODE]: 200,
  [SemanticAttributes.HTTP_STATUS_TEXT]: 'OK',
  [SemanticAttributes.HTTP_FLAVOR]: '1.1',

  // Cliente
  [SemanticAttributes.HTTP_CLIENT_IP]: '192.168.1.1',
  [SemanticAttributes.HTTP_USER_AGENT]: 'Mozilla/5.0...',
};

Atributos padrão de banco de dados

typescriptimport { SemanticAttributes } from '@opentelemetry/semantic-conventions';

const dbAttributes = {
  // Sistema de banco
  [SemanticAttributes.DB_SYSTEM]: 'postgresql',

  // Operação
  [SemanticAttributes.DB_OPERATION]: 'SELECT',
  [SemanticAttributes.DB_STATEMENT]: 'SELECT * FROM users WHERE id = $1',

  // Nome e tabela
  [SemanticAttributes.DB_NAME]: 'app_db',
  [SemanticAttributes.DB_USER]: 'app_user',

  // Conexão
  [SemanticAttributes.DB_CONNECTION_STRING]: 'postgresql://localhost:5432/app_db',
  [SemanticAttributes.DB_JDBC_DRIVER]: 'org.postgresql.Driver',
};

Padrões de produção

Padrão 1: Sampling inteligente

typescript// src/tracing/sampler.ts
import { ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';

// Sampling baseado em ID de trace (consistente)
const idBasedSampler = new TraceIdRatioBasedSampler(0.1); // 10% dos traces

// Sampling baseado em parent (se parent é amostrado, children também são)
const parentBasedSampler = new ParentBasedSampler({
  root: idBasedSampler,
  remoteParentSampled: new TraceIdRatioBasedSampler(0.05),
});

const provider = new NodeTracerProvider({
  sampler: parentBasedSampler,
  // ...resto da configuração
});

Padrão 2: Batching para reduzir overhead

typescript// src/telemetry/processor.ts
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';

// Batching de spans
const batchProcessor = new BatchSpanProcessor(
  new OTLPTraceExporter({
    url: 'http://otel-collector:4317/v1/traces',
    // Enviar em batches de 100 spans ou 5 segundos
    batchSizeLimit: 100,
    batchTimeout: 5000,
  })
);

Padrão 3: Resource attributes centralizados

typescript// src/telemetry/resource.ts
import { detectResources } from '@opentelemetry/resources';

const detectedResources = detectResources({
  detectors: [
    // Detecta automaticamente: AWS, GCP, Azure, containers, etc.
  ],
});

export const resource = detectedResources;

Integração com OTel Collector

Configuração do Collector

yaml# otel-collector-config.yaml
receivers:
  # Receber traces via OTLP
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

  # Receber traces de Jaeger (legado)
  jaeger:
    protocols:
      thrift_compact:
        endpoint: jaeger:14268/api/traces

processors:
  # Batching de spans
  batch:
    timeout: 5s
    send_batch_size: 1000

  # Adicionar resource attributes a todos os spans
  resource:
    attributes:
      - key: service.name
        value: api-gateway
      - key: service.version
        value: 2.1.0
      - key: deployment.environment
        value: production

exporters:
  # Enviar para Jaeger
  jaeger:
    endpoint: jaeger:14250/api/traces
    tls:
      insecure: true

  # Enviar para Prometheus (metrics)
  prometheus:
    endpoint: "0.0.0.0:9090/metrics"

  # Enviar logs para Loki
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

Deploy do Collector

yaml# k8s/otel-collector-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
spec:
  replicas: 3
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
      - name: otel-collector
        image: otel/opentelemetry-collector-contrib:latest
        args:
        - --config=/etc/otel-collector-config.yaml
        ports:
        - containerPort: 4317  # OTLP gRPC
        - containerPort: 4318  # OTLP HTTP
        - containerPort: 14268 # Jaeger gRPC
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
        volumeMounts:
        - name: config-volume
          mountPath: /etc/otel-collector-config.yaml
          readOnly: true
      volumes:
      - name: config-volume
        configMap:
          name: otel-collector-config

Monitoramento de overhead de OTel

Métricas do próprio collector

yaml# otel-collector-config.yaml
receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: 'otel-collector'
          static_configs:
            - targets: ['0.0.0.0:8888']

service:
  pipelines:
    traces:
      receivers: [otlp, jaeger]
      processors: [batch]
      exporters: [jaeger]

    metrics:
      receivers: [prometheus]
      processors: [batch]
      exporters: [prometheus]

Alertas de overhead

yaml# alerts/otel-overhead.yaml
groups:
  - name: otel-overhead
    interval: 30s
    rules:
      - alert: HighSpanProcessingTime
        expr: histogram_quantile(0.99, rate(otel_collector_exporter_enqueue_span_latency_seconds[5m])) > 0.1
        annotations:
          summary: "Processing time para 99th percentile de spans está alto"
          description: "O collector está levando mais de 100ms para processar spans"

      - alert: HighDropRate
        expr: rate(otel_collector_processor_spans_dropped_total[5m]) > 10
        annotations:
          summary: "High drop rate de spans"
          description: "O collector está dropando mais de 10 spans/segundo"

      - alert: HighMemoryUsage
        expr: container_memory_usage_bytes{container="otel-collector"} / container_spec_memory_limit_bytes{container="otel-collector"} > 0.8
        annotations:
          summary: "High memory usage do Otel Collector"
          description: "O collector está usando mais de 80% da memória alocada"

Plano de implementação em 60 dias

Semanas 1-2: Fundação

  • Instalar OTel Collector no cluster
  • Configurar exporters (Jaeger, Prometheus, Loki)
  • Definir semantic conventions padrão
  • Criar pacotes de instrumentação

Semanas 3-4: Instrumentação

  • Instrumentar handlers HTTP de API
  • Instrumentar operações de banco de dados
  • Adicionar spans customizados para lógica de negócio
  • Implementar context propagation

Semanas 5-6: Otimização

  • Configurar sampling inteligente
  • Implementar batching para reduzir overhead
  • Criar dashboards de observabilidade unificados
  • Validar ganhos de performance e qualidade de debugging

Conclusão

OpenTelemetry em 2026 é o padrão unificado para observabilidade que elimina fragmentação de logs, metrics e traces. Ao implementar OTel, organizações reduzem esforço de instrumentação, padronizam dados de observabilidade e facilitam migração entre ferramentas sem mudar código de aplicação.

A maturidade de OpenTelemetry está estabelecida: semantic conventions bem definidas, SDKs maduros para todas as principais linguagens, suporte nativo dos principais provedores de cloud e um ecossistema robusto de ferramentas de visualização.

A chave é começar com instrumentação estruturada: usar semantic conventions, implementar sampling inteligente para reduzir overhead, e configurar collectors apropriados para seus requisitos específicos.

Pergunta prática de encerramento: Sua organização pode debugar problemas de produção tracing uma requisição completa através de logs, metrics e spans correlacionados em um único contexto, ou precisa acessar múltiplos dashboards fragmentados?


Precisa implementar OpenTelemetry para unificar observabilidade e melhorar capacidade de debugging em produção? Fale com especialistas da Imperialis sobre arquitetura de observabilidade, implementação de OTel e estratégias de instrumentação.

Fontes

Leituras relacionadas