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.
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 exporterArquitetura 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/pushDeploy 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-configMonitoramento 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
- OpenTelemetry Documentation — Documentação oficial de OpenTelemetry
- OpenTelemetry Collector — Especificação do Collector
- Semantic Conventions — Semantic attributes padrão
- OpenTelemetry JavaScript SDK — SDK JavaScript
- OpenTelemetry Registry — Instrumentações e exporters disponíveis