End-to-End Observability Implementation
This document describes the implementation of end-to-end distributed tracing across the ERP-Unlocked monorepo, following the auto-instrumentation approach outlined in the observability plan.
🎯 Implementation Overview
Section titled “🎯 Implementation Overview”The implementation provides reliable, end-to-end distributed tracing from user actions in the webapp through to LLM processing in the pdf-worker, using OpenTelemetry auto-instrumentation and W3C Trace Context propagation.
Key Features
Section titled “Key Features”- End-to-end correlation: Single trace from webapp → pdf-api → Celery → pdf-worker → LLM spans
- User continuity: User context propagated via OpenTelemetry Baggage
- LLM observability: Langfuse integration for LLM trace visibility
- Auto-instrumentation: Minimal custom code, maximum coverage
- Security preserved: Service-to-service auth via X-Service-Token maintained
🏗️ Architecture
Section titled “🏗️ Architecture”┌─────────────┐ HTTP + traceparent ┌─────────────┐ Celery + context ┌─────────────┐│ Webapp │ ────────────────────────► │ pdf-api │ ────────────────────► │ pdf-worker ││ (Astro/Node)│ │ (FastAPI) │ │ (Celery) │└─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ Baggage: user.id, session.id │ │ │ │ │ ▼ ▼ ▼┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ BetterStack │ │ BetterStack │ │ BetterStack ││ (Traces) │ │ (Traces) │ │ (Traces) │└─────────────┘ └─────────────┘ └─────────────┘ │ │ ▼ ┌─────────────┐ │ Langfuse │ │ (LLM Traces)│ └─────────────┘📦 Implementation Details
Section titled “📦 Implementation Details”1. Webapp (Astro/Node.js)
Section titled “1. Webapp (Astro/Node.js)”Dependencies Added
Section titled “Dependencies Added”{ "@opentelemetry/instrumentation-undici": "^0.50.0"}Key Changes
Section titled “Key Changes”otel-init.cjs - Enhanced with Undici instrumentation:
const { UndiciInstrumentation } = require('@opentelemetry/instrumentation-undici');
const sdk = new NodeSDK({ traceExporter, instrumentations: [ getNodeAutoInstrumentations({ '@opentelemetry/instrumentation-fs': { enabled: false }, }), new UndiciInstrumentation(), // ← Added for fetch() trace propagation new PinoInstrumentation({ logHook: (span, record, _level) => { record['resource.service.name'] = process.env.OTEL_SERVICE_NAME; }, }), ],});fetchWithTrace.ts - New utility for trace propagation:
export async function fetchWithUserContext( url: string, init: RequestInit = {}, userContext?: { userId?: string; sessionId?: string }) { // Creates OpenTelemetry Baggage with user context // Injects trace context and baggage into HTTP headers // Ensures end-to-end correlation}API Routes Updated:
apps/webapp/src/pages/api/pdf-documents/index.tsapps/webapp/src/pages/api/pdf-documents/[id]/process.ts
Both now use fetchWithUserContext() instead of plain fetch() to propagate user context.
Development Setup
Section titled “Development Setup”{ "scripts": { "dev": "cross-env NODE_OPTIONS=\"--require ./otel-init.cjs\" astro dev" }}2. pdf-api (FastAPI)
Section titled “2. pdf-api (FastAPI)”Dependencies Added
Section titled “Dependencies Added”dependencies = [ # Auto-instrumentation packages for full observability coverage "opentelemetry-instrumentation-fastapi==0.43b0", "opentelemetry-instrumentation-psycopg2==0.43b0", "opentelemetry-instrumentation-redis==0.43b0", "opentelemetry-instrumentation-celery==0.43b0", "opentelemetry-instrumentation-logging==0.43b0",]Key Features
Section titled “Key Features”- FastAPI auto-instrumentation: Automatic span creation for all routes
- Database tracing: psycopg2 instrumentation for PostgreSQL queries
- Redis tracing: Redis operation visibility
- Celery producer: Automatic context propagation to Celery tasks
- Logging correlation: Trace IDs in structured logs
Environment Configuration
Section titled “Environment Configuration”OTEL_RESOURCE_ATTRIBUTES=service.name=pdf-processor-api,service.version=1.0.0,deployment.environment=productionOTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT}OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS}OTEL_TRACES_EXPORTER=otlp3. pdf-worker (Celery)
Section titled “3. pdf-worker (Celery)”Dependencies Added
Section titled “Dependencies Added”dependencies = [ # Auto-instrumentation packages for full observability coverage "opentelemetry-instrumentation-celery==0.43b0", "opentelemetry-instrumentation-psycopg2==0.43b0", "opentelemetry-instrumentation-redis==0.43b0", "opentelemetry-instrumentation-logging==0.43b0", # Langfuse integration for LLM observability "langfuse>=2.0.0", "langfuse-opentelemetry>=1.0.0",]Key Features
Section titled “Key Features”- Celery auto-instrumentation: Automatic task span creation and context propagation
- LLM observability: Langfuse integration for LLM trace visibility
- Database tracing: psycopg2 instrumentation for PostgreSQL queries
- Redis tracing: Redis operation visibility
Langfuse Integration
Section titled “Langfuse Integration”langfuse_config.py:
def initialize_langfuse(): langfuse = Langfuse( public_key=os.getenv("LANGFUSE_PUBLIC_KEY"), secret_key=os.getenv("LANGFUSE_SECRET_KEY"), host=os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com"), ) LangfuseInstrumentor().instrument() # Links to OpenTelemetry return langfusecelery_app.py:
# Initialize Langfuse for LLM observability (optional)try: import langfuse_config # This will initialize Langfuse if configuredexcept ImportError: pass # Langfuse not available, continue without itEnvironment Configuration
Section titled “Environment Configuration”OTEL_RESOURCE_ATTRIBUTES=service.name=pdf-processor-worker,service.version=1.0.0,deployment.environment=production# Langfuse configuration for LLM observability (optional)LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY:-}LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY:-}LANGFUSE_HOST=${LANGFUSE_HOST:-https://cloud.langfuse.com}🔧 Configuration
Section titled “🔧 Configuration”Environment Variables
Section titled “Environment Variables”Required
Section titled “Required”# Better Stack/OpenTelemetryOTEL_EXPORTER_OTLP_ENDPOINT=https://in-otel.betterstack.comOTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer your-better-stack-token"OTEL_TRACES_EXPORTER=otlp
# Service URLsPDF_PROCESSOR_URL=http://pdf-api:8000PDF_PROCESSOR_TOKEN=your-service-tokenOptional (Langfuse)
Section titled “Optional (Langfuse)”LANGFUSE_PUBLIC_KEY=pk_...LANGFUSE_SECRET_KEY=sk_...LANGFUSE_HOST=https://cloud.langfuse.comDocker Compose
Section titled “Docker Compose”The production docker-compose.prod.yml already includes:
- OpenTelemetry instrumentation via
opentelemetry-instrument - Proper environment variable configuration
- Service-to-service networking
🧪 Validation
Section titled “🧪 Validation”Automated Validation Script
Section titled “Automated Validation Script”Run the validation script to check the implementation:
npx tsx scripts/validate-observability.tsThis script validates:
- Environment variable configuration
- Webapp instrumentation (Undici, trace propagation)
- pdf-api health and connectivity
- Celery worker configuration
- Langfuse integration (if configured)
Manual Validation
Section titled “Manual Validation”- Upload a test PDF document through the webapp
- Check Better Stack dashboard for:
- Single trace spanning webapp → pdf-api → pdf-worker
- User context in span attributes (
user.id,session.id) - Celery task spans linked to parent traces
- Check Langfuse dashboard (if configured) for:
- LLM traces linked to OpenTelemetry spans
- End-to-end visibility from user action to LLM generation
📊 Expected Trace Flow
Section titled “📊 Expected Trace Flow”1. User Uploads PDF
Section titled “1. User Uploads PDF”webapp: POST /api/pdf-documents├── http: fetch to pdf-api (with traceparent + baggage)└── pdf-api: POST /pdf-documents/{id}/process ├── db: insert pdf_document ├── celery: enqueue process_document task (with context) └── pdf-worker: process_document task ├── db: update document status ├── llm: extract data from PDF (Langfuse span) ├── db: insert extracted orders └── llm: validate extracted data (Langfuse span)2. Span Attributes
Section titled “2. Span Attributes”Webapp spans:
user.id: User identifier from Clerksession.id: Session identifierhttp.url: Target URLhttp.method: HTTP method
pdf-api spans:
user.id: From Baggagesession.id: From Baggagecelery.task_name: Task being enqueueddb.operation: Database operation type
pdf-worker spans:
user.id: From Baggagesession.id: From Baggagecelery.task_name: Task being executeddocument.id: Document being processedllm.model: LLM model used (from Langfuse)
🚀 Deployment
Section titled “🚀 Deployment”Development
Section titled “Development”# Start with observability enabledpnpm --filter webapp devProduction
Section titled “Production”# Deploy with docker-compose.prod.ymldocker compose -f docker-compose.prod.yml up -dThe production deployment automatically includes:
- OpenTelemetry instrumentation
- Proper environment configuration
- Service-to-service trace propagation
🔍 Troubleshooting
Section titled “🔍 Troubleshooting”Common Issues
Section titled “Common Issues”-
No trace propagation from webapp
- Check if
@opentelemetry/instrumentation-undiciis installed - Verify
otel-init.cjsis being preloaded - Ensure
fetchWithUserContextis being used
- Check if
-
Missing Celery spans
- Verify
opentelemetry-instrumentation-celeryis installed - Check that both producer (pdf-api) and consumer (pdf-worker) have the package
- Ensure
opentelemetry-instrumentis used in Docker commands
- Verify
-
No Langfuse traces
- Verify Langfuse environment variables are set
- Check that
langfuse-opentelemetryis installed - Ensure
langfuse_config.pyis imported in celery_app.py
-
Missing user context
- Verify
fetchWithUserContextis used instead of plainfetch - Check that user context is being passed correctly
- Ensure Baggage is being created with proper keys
- Verify
Debug Commands
Section titled “Debug Commands”# Check webapp instrumentationcurl -H "Traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" \ http://localhost:4321/api/pdf-documents
# Check pdf-api healthcurl http://localhost:8000/health
# Check Celery worker statusdocker compose exec pdf-worker celery -A celery_app inspect ping
# Validate observability setupnpx tsx scripts/validate-observability.ts📈 Monitoring
Section titled “📈 Monitoring”SigNoz Dashboards
Section titled “SigNoz Dashboards”Create dashboards for:
- End-to-end request latency: webapp → pdf-api → pdf-worker
- Error rates by service: Track failures across the pipeline
- User activity: Filter by user.id to see individual user flows
- LLM performance: Track LLM call latency and success rates
Key Metrics
Section titled “Key Metrics”- Trace completion rate: Percentage of traces that complete end-to-end
- User context propagation: Percentage of spans with user.id attribute
- LLM trace linkage: Percentage of LLM calls linked to OpenTelemetry spans
- Service latency: P50, P95, P99 latencies for each service
🔄 Future Enhancements
Section titled “🔄 Future Enhancements”- Custom span attributes: Add business-specific attributes (document type, order count, etc.)
- Error tracking: Enhanced error correlation across services
- Performance optimization: Sampling strategies for high-volume scenarios
- Alerting: Set up alerts for trace failures or high latency
- Custom metrics: Business metrics correlated with traces
This implementation provides a solid foundation for end-to-end observability while maintaining the existing security model and minimizing custom code complexity.