Cap 6: Instrumentación
Auto-instrumentación vs Manual
graph LR
AUTO[Auto-instrumentación\nCero código\nLibrerías contrib] -->|cubre| FW[Frameworks\nHTTP, DB, Queue]
MAN[Manual\nTu código de negocio\nContexto específico] -->|cubre| BIZ[Lógica de negocio\nReglas de dominio]
AUTO -.->|complementa| MAN
Auto-instrumentación — Las librerías contrib de OTel instrumentan automáticamente frameworks populares (Flask, Express, psycopg2, redis, etc.) sin que modifiques tu código.
Manual — Instrumentas la lógica de negocio con contexto que las librerías no pueden inferir: order.id, payment.tier, feature.flags.
La combinación correcta: auto-instrumentación para el plumbing (HTTP, DB), manual para el dominio.
Python — Setup completo
Instalar dependencias
pip install \
opentelemetry-sdk \
opentelemetry-exporter-otlp-proto-grpc \
opentelemetry-instrumentation-fastapi \
opentelemetry-instrumentation-sqlalchemy \
opentelemetry-instrumentation-redis \
opentelemetry-instrumentation-httpx
Configuración del SDK
# otel_setup.py — ejecutar al inicio de la aplicación
from opentelemetry import trace, metrics
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk._logs import LoggingHandler
import logging
import os
OTEL_ENDPOINT = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
SERVICE_NAME = os.getenv("OTEL_SERVICE_NAME", "my-service")
def setup_telemetry():
resource = Resource.create({
"service.name": SERVICE_NAME,
"service.version": os.getenv("APP_VERSION", "0.0.0"),
"deployment.environment": os.getenv("ENV", "development"),
})
# Traces
tracer_provider = TracerProvider(resource=resource)
tracer_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint=OTEL_ENDPOINT))
)
trace.set_tracer_provider(tracer_provider)
# Metrics
metric_reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint=OTEL_ENDPOINT),
export_interval_millis=60_000,
)
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
metrics.set_meter_provider(meter_provider)
# Logs
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter(endpoint=OTEL_ENDPOINT))
)
set_logger_provider(logger_provider)
# Conectar con logging estándar
handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider)
logging.getLogger().addHandler(handler)
Auto-instrumentación con FastAPI
# main.py
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
from otel_setup import setup_telemetry
setup_telemetry()
app = FastAPI()
# Auto-instrumentar FastAPI — crea spans para cada endpoint
FastAPIInstrumentor.instrument_app(app)
# Auto-instrumentar SQLAlchemy — crea spans para queries SQL
SQLAlchemyInstrumentor().instrument(engine=engine)
# Auto-instrumentar Redis
RedisInstrumentor().instrument()
Con esto, cada request HTTP y query SQL genera spans automáticamente sin más código.
Instrumentación manual de dominio
from opentelemetry import trace
from opentelemetry.trace import StatusCode
import logging
tracer = trace.get_tracer("order-service", "1.0.0")
logger = logging.getLogger("order-service")
meter = metrics.get_meter("order-service", "1.0.0")
orders_counter = meter.create_counter("orders.created", unit="1")
order_value = meter.create_histogram("orders.value", unit="USD")
async def create_order(user_id: str, items: list, db) -> dict:
with tracer.start_as_current_span("create-order") as span:
span.set_attribute("enduser.id", user_id)
span.set_attribute("order.items_count", len(items))
total = sum(item["price"] for item in items)
span.set_attribute("order.total_usd", total)
logger.info("Creating order", extra={
"enduser.id": user_id,
"order.items_count": len(items),
"order.total_usd": total,
})
try:
order = await db.create_order(user_id, items)
orders_counter.add(1, {"tier": get_user_tier(user_id)})
order_value.record(total, {"currency": "USD"})
span.add_event("order persisted", {"order.id": order["id"]})
return order
except Exception as e:
span.record_exception(e)
span.set_status(StatusCode.ERROR, str(e))
logger.error("Order creation failed", extra={"error": str(e)})
raise
Node.js — Setup completo
Instalar dependencias
npm install \
@opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc \
@opentelemetry/exporter-metrics-otlp-grpc \
@opentelemetry/semantic-conventions
Configuración (debe cargarse primero)
// instrumentation.ts — importar ANTES que cualquier otro módulo
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { Resource } from '@opentelemetry/resources';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME ?? 'my-service',
[ATTR_SERVICE_VERSION]: process.env.APP_VERSION ?? '0.0.0',
'deployment.environment': process.env.NODE_ENV ?? 'development',
}),
// Auto-instrumenta: Express, HTTP, grpc, pg, redis, etc.
instrumentations: [getNodeAutoInstrumentations()],
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4317',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4317',
}),
exportIntervalMillis: 60_000,
}),
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());
Cargar antes que todo:
// package.json
{
"scripts": {
"start": "node --require ./dist/instrumentation.js dist/index.js"
}
}
Instrumentación manual en Node.js
import { trace, metrics, SpanStatusCode } from '@opentelemetry/api';
import { ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions';
const tracer = trace.getTracer('order-service', '1.0.0');
const meter = metrics.getMeter('order-service', '1.0.0');
const ordersCounter = meter.createCounter('orders.created', { unit: '1' });
const orderDuration = meter.createHistogram('orders.processing.duration', { unit: 's' });
async function createOrder(userId: string, items: Item[]): Promise<Order> {
return tracer.startActiveSpan('create-order', async (span) => {
span.setAttributes({
'enduser.id': userId,
'order.items_count': items.length,
});
const start = performance.now();
try {
const order = await db.createOrder(userId, items);
ordersCounter.add(1, { 'order.status': 'success' });
orderDuration.record((performance.now() - start) / 1000);
span.setStatus({ code: SpanStatusCode.OK });
return order;
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
ordersCounter.add(1, { 'order.status': 'error' });
throw error;
} finally {
span.end();
}
});
}
Variables de entorno para configuración
Una ventaja de OTel es que mucha configuración se puede hacer sin código:
# Identificación del servicio
export OTEL_SERVICE_NAME=order-service
export OTEL_SERVICE_VERSION=2.1.0
# Exporter
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# Sampling — 10% en producción
export OTEL_TRACES_SAMPLER=traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
# Atributos del resource
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,k8s.namespace.name=payments
Librerías contrib disponibles
OTel tiene instrumentación lista para usar para los frameworks más populares:
Python:
opentelemetry-instrumentation-fastapiopentelemetry-instrumentation-djangoopentelemetry-instrumentation-flaskopentelemetry-instrumentation-sqlalchemyopentelemetry-instrumentation-psycopg2opentelemetry-instrumentation-redisopentelemetry-instrumentation-httpxopentelemetry-instrumentation-celery
Node.js (via @opentelemetry/auto-instrumentations-node):
- Express, Fastify, NestJS
- pg, mysql2, mongodb
- redis, ioredis
- grpc, http, https
- aws-sdk, kafka.js