Skip to content

ERP-Unlocked Observability Architecture

This document outlines the comprehensive observability strategy implemented across the ERP-Unlocked monorepo, covering logging, metrics, tracing, and LLM observability.

Our observability architecture follows a multi-layered approach with distinct responsibilities:

  • Better Stack: Primary logging and application metrics
  • Langfuse: LLM-specific observability and tracing
  • OpenTelemetry: Distributed tracing and metrics collection

Purpose: Primary logging destination for all applications Implementation: Structured logging with JSON format

# Environment variables
BETTER_STACK_SOURCE_TOKEN=your_token_here
BETTER_STACK_ENDPOINT=https://in.logs.betterstack.com
LOG_LEVEL=info
  • Structured Logging: JSON-formatted logs with consistent fields
  • Trace Correlation: OpenTelemetry trace IDs embedded in logs
  • Service Identification: Each service tagged with service.name
  • Error Tracking: Comprehensive error logging with context
{
"timestamp": "2025-01-24T10:30:00.000Z",
"level": "info",
"service": "pdf-worker",
"message": "Processing document chunk",
"trace_id": "abc123...",
"span_id": "def456...",
"document_id": "uuid-here",
"chunk_number": 2
}

Purpose: Specialized observability for LLM operations (Gemini API) Implementation: Native Langfuse 3.x API with isolated OpenTelemetry tracer

# Environment variables
LANGFUSE_PUBLIC_KEY=your_public_key
LANGFUSE_SECRET_KEY=your_secret_key
LANGFUSE_HOST=https://cloud.langfuse.com
LANGFUSE_FLUSH_INTERVAL=10
LANGFUSE_TRACE_TIMEOUT=5
  • Session Tracking: User and session correlation
  • Cost Tracking: Automatic cost calculation for LLM requests
  • Token Usage: Input/output token monitoring
  • Quality Scoring: Response quality assessment
  • Trace Hierarchy: Parent-child span relationships
# Main task span
span = langfuse_client.start_span(
name="process_document_task",
input={"document_id": document_id},
metadata={"user_id": user_id}
)
span.update_trace(session_id=document_id, user_id=user_id)
# Child generation span
generation = span.start_generation(
name="process_single_chunk",
model="gemini-3-flash-preview",
input={"chunk_index": chunk_index}
)
# Update with results
generation.update(
output=result,
usage_details={
"input": input_tokens,
"output": output_tokens,
"total": total_tokens
}
)
generation.end()
span.end()

Purpose: Distributed tracing and metrics collection Implementation: Dual tracer architecture

# Primary tracer (Better Stack)
_primary_tracer_provider = TracerProvider()
tracer = otel_trace.get_tracer(__name__, tracer_provider=_primary_tracer_provider)
# Langfuse tracer (isolated for LLM operations)
_langfuse_tracer_provider = TracerProvider()
  • Distributed Tracing: End-to-end request tracking
  • Custom Metrics: Application-specific measurements
  • Span Attributes: Rich metadata for filtering
  • Error Tracking: Exception and error propagation
# Custom metrics
llm_request_duration = meter.create_histogram(
name="llm_request_duration_ms",
description="Duration of LLM API requests in milliseconds",
unit="ms"
)
llm_tokens_total = meter.create_counter(
name="llm_tokens_total",
description="Total token consumption",
unit="tokens"
)

Location: apps/pdf-worker/ Observability: Full integration with all components

  • Document processing duration
  • Chunk processing success rates
  • LLM API call metrics
  • Database operation timing
  • Error rates and types
process_document_task (span)
├── download_document (span)
├── chunk_document (span)
├── process_single_chunk (generation)
│ ├── gemini_api_call (span)
│ └── json_validation (span)
├── merge_results (span)
└── save_to_database (span)

Location: apps/pdf-api/ Observability: HTTP request tracing and metrics

  • API endpoint response times
  • Request/response sizes
  • Authentication success rates
  • Database query performance
  • Error response codes
http_request (span)
├── authentication (span)
├── request_validation (span)
├── business_logic (span)
├── database_operations (span)
└── response_serialization (span)

Location: apps/webapp/ Observability: Client-side performance and user interactions

  • Page load times
  • API call durations
  • User interaction tracking
  • Error rates
  • Performance metrics

Purpose: Application monitoring and alerting Configuration: Automatic log parsing and visualization

  • Service health status
  • Error rates and trends
  • Performance metrics
  • User activity patterns
  • System resource usage

Purpose: LLM-specific monitoring Configuration: Langfuse provides built-in dashboard interface

  • LLM request volume
  • Cost tracking and trends
  • Response quality scores
  • Token usage patterns
  • Error analysis

Purpose: Distributed tracing and metrics collection Configuration: Direct OpenTelemetry data export

  • Distributed tracing visualization
  • Custom metrics collection
  • Performance analysis
  • Error tracking
  • Cross-service correlation
- alert: HighErrorRate
expr: rate(http_requests_total{status="error"}[5m]) > 0.1
for: 2m
labels:
severity: critical
- alert: HighLLMCosts
expr: sum(rate(llm_request_cost_usd[1h])) * 3600 > 100
for: 5m
labels:
severity: warning
- alert: HighLatency
expr: histogram_quantile(0.95, rate(http_request_duration_ms_bucket[5m])) > 5000
for: 3m
labels:
severity: warning
- alert: LowLLMQuality
expr: avg(llm_response_quality_score) < 0.7
for: 10m
labels:
severity: warning
- alert: HighTokenUsage
expr: rate(llm_tokens_total[5m]) > 10000
for: 5m
labels:
severity: warning
  • Use consistent log formats across all services
  • Include trace IDs for correlation
  • Add relevant context to error messages
  • Use appropriate log levels
  • Follow OpenTelemetry semantic conventions
  • Use descriptive names with units
  • Include relevant labels for filtering
  • Avoid high-cardinality labels
  • Keep spans focused and meaningful
  • Include relevant attributes
  • Maintain proper parent-child relationships
  • Handle errors gracefully
  • Monitor LLM usage patterns
  • Set up cost alerts and budgets
  • Optimize prompt efficiency
  • Track cost per successful operation
  • Track key performance indicators
  • Set up latency alerts
  • Monitor resource usage
  • Analyze performance trends
  1. Check OpenTelemetry configuration
  2. Verify tracer initialization
  3. Ensure proper span creation
  4. Check for context propagation issues
  1. Monitor token usage patterns
  2. Review prompt efficiency
  3. Check for retry loops
  4. Analyze cost per operation
  1. Check latency percentiles
  2. Monitor resource usage
  3. Analyze error rates
  4. Review database performance
topk(10, llm_request_cost_usd) by (operation)
histogram_quantile(0.95, rate(http_request_duration_ms_bucket[5m])) > 3000
rate(http_requests_total{status="error"}[5m]) / rate(http_requests_total[5m])
Terminal window
BETTER_STACK_SOURCE_TOKEN=your_token
BETTER_STACK_ENDPOINT=https://in.logs.betterstack.com
LOG_LEVEL=info
Terminal window
LANGFUSE_PUBLIC_KEY=your_public_key
LANGFUSE_SECRET_KEY=your_secret_key
LANGFUSE_HOST=https://cloud.langfuse.com
LANGFUSE_FLUSH_INTERVAL=10
LANGFUSE_TRACE_TIMEOUT=5
LANGFUSE_DEBUG_LOGGING=false
Terminal window
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-endpoint.com
OTEL_SERVICE_NAME=your-service
OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0
  1. Advanced Analytics: Machine learning-based anomaly detection
  2. Cost Optimization: Automated prompt optimization
  3. Performance Tuning: Dynamic scaling based on metrics
  4. User Experience: Real-time user behavior tracking
  5. Security Monitoring: Threat detection and response
  1. Slack Notifications: Real-time alert delivery
  2. PagerDuty Integration: Incident management
  3. Grafana Dashboards: Advanced visualization
  4. DataDog Integration: Enterprise monitoring
  5. Custom Analytics: Business intelligence integration

Last Updated: January 2025 Version: 1.0 Maintainer: ERP-Unlocked Development Team