Skip to content

Observability & Monitoring: Seeing What Happens

Overview

You can't fix what you can't see. Observability is the foundation of production systems.


Structured Logging

Agent Execution Logs

class StructuredAgentLogging:
    """Log every agent action in structured format"""

    def log_execution_step(self, step_data):
        """Log structured step data"""

        log_entry = {
            'timestamp': time.time(),
            'request_id': step_data.request_id,
            'agent_id': step_data.agent_id,
            'step_number': step_data.step_number,
            'action': step_data.action,
            'input': step_data.input,
            'output': step_data.output,
            'latency_ms': step_data.latency_ms,
            'tokens_used': step_data.tokens,
            'cost': step_data.cost,
            'status': 'success' | 'error' | 'timeout',
            'metadata': {
                'model': step_data.model,
                'temperature': step_data.temperature
            }
        }

        # Log to structured storage
        self.structured_logger.info(log_entry)

    def query_logs(self, request_id):
        """Retrieve all logs for a request"""

        logs = self.structured_logger.filter(
            request_id=request_id
        ).order_by('timestamp')

        return logs

Trace Collection

End-to-End Request Tracing

class RequestTracing:
    """Track request through entire system"""

    def __init__(self):
        self.tracer = Tracer()  # e.g., Jaeger

    def trace_request(self, request):
        """Create trace for full request lifecycle"""

        with self.tracer.start_span("handle_request") as span:
            span.set_tag("request_id", request.id)
            span.set_tag("user_id", request.user_id)

            # Validation
            with self.tracer.start_span("validate"):
                self.validate(request)

            # Routing
            with self.tracer.start_span("route"):
                agent = self.router.select(request)

            # Execution
            with self.tracer.start_span("execute"):
                result = agent.execute(request)

            # Monitoring
            with self.tracer.start_span("monitor"):
                self.monitor(result)

            return result

    def view_trace(self, trace_id):
        """Visualize full request trace"""

        # Show Gantt chart of operations
        trace = self.tracer.get_trace(trace_id)
        self.display_trace_chart(trace)

Metrics & Dashboards

Key Production Metrics

class ProductionMetrics:
    """Track production health"""

    def __init__(self):
        self.metrics = {
            # Throughput
            'requests_per_second': Counter(),
            'requests_total': Counter(),

            # Latency
            'latency_p50': Histogram(),
            'latency_p95': Histogram(),
            'latency_p99': Histogram(),

            # Quality
            'accuracy': Gauge(),
            'error_rate': Gauge(),

            # Cost
            'cost_per_request': Histogram(),
            'monthly_cost': Gauge(),

            # Safety
            'policy_violations': Counter(),
            'safety_issues': Counter()
        }

    def record_request(self, request, result):
        """Record metrics after request"""

        self.metrics['requests_per_second'].inc()
        self.metrics['latency_p95'].observe(result.latency)

        if result.success:
            self.metrics['accuracy'].set(
                self.calculate_accuracy(result)
            )
        else:
            self.metrics['error_rate'].inc()

        self.metrics['cost_per_request'].observe(result.cost)

Alerting Strategies

Smart Alerting

class AlertingStrategy:
    """Only alert on real issues"""

    def __init__(self):
        self.alert_rules = [
            {
                'name': 'high_error_rate',
                'metric': 'error_rate',
                'threshold': 0.05,
                'duration': '5m',
                'severity': 'critical'
            },
            {
                'name': 'high_latency_p99',
                'metric': 'latency_p99',
                'threshold': 5000,  # 5 seconds
                'duration': '10m',
                'severity': 'warning'
            },
            {
                'name': 'cost_spike',
                'metric': 'cost_per_request',
                'threshold': 1.0,  # $1/request
                'duration': '1m',
                'severity': 'warning'
            },
            {
                'name': 'safety_violation',
                'metric': 'policy_violations',
                'threshold': 1,
                'duration': '0m',  # Immediate
                'severity': 'critical'
            }
        ]

    def check_alerts(self):
        """Evaluate alert rules"""

        for rule in self.alert_rules:
            current_value = self.get_metric(
                rule['metric'],
                duration=rule['duration']
            )

            if current_value > rule['threshold']:
                self.send_alert(
                    title=rule['name'],
                    severity=rule['severity'],
                    metric=rule['metric'],
                    value=current_value,
                    threshold=rule['threshold']
                )

Post-Incident Analysis

Incident Review Process

class PostIncidentAnalysis:
    """Learn from incidents"""

    def analyze_incident(self, incident_id):
        """Comprehensive incident analysis"""

        incident = self.incidents.get(incident_id)

        analysis = {
            'timeline': self.build_timeline(incident),
            'root_cause': self.identify_root_cause(incident),
            'contributing_factors': self.find_contributing_factors(incident),
            'impact': self.quantify_impact(incident),
            'recovery_actions': self.identify_recovery_actions(incident),
            'preventive_measures': self.suggest_preventions(incident)
        }

        # Generate report
        report = self.generate_report(analysis)

        # Share findings
        self.notify_team(report)
        self.add_to_knowledge_base(report)

    def build_timeline(self, incident):
        """Extract timeline from logs"""

        logs = self.structured_logger.filter(
            request_ids=incident.affected_requests
        ).order_by('timestamp')

        timeline = []
        for log in logs:
            timeline.append({
                'time': log.timestamp,
                'action': log.action,
                'status': log.status,
                'error': log.error
            })

        return timeline

3 Warnings ⚠️

Warning 1: Too Much Logging

# ❌ WRONG
# Log everything at high verbosity
for step in agent.steps:
    logger.debug(f"Step: {step}")
    logger.debug(f"Input: {step.input}")
    logger.debug(f"Output: {step.output}")
    logger.debug(f"Tokens: {step.tokens}")
    # Logs grow to GBs/day

# ✅ RIGHT
# Log only important events
logger.info({
    'event': 'step_complete',
    'step_number': step.number,
    'status': step.status,
    'latency': step.latency
})
# Logs stay manageable

Warning 2: No Aggregation

# ❌ WRONG
# Store every log event individually
for event in events:
    store.save(event)  # Millions of rows

# Hard to analyze later

# ✅ RIGHT
# Aggregate metrics
metrics.latency_histogram.observe(latency)
metrics.error_counter.inc()
metrics.cost_gauge.set(cost)

# Queryable metrics, not raw logs

Warning 3: Missing Context

# ❌ WRONG
logger.error(f"Request failed")
# No context! Which request? Why failed?

# ✅ RIGHT
logger.error(
    "Request failed",
    extra={
        'request_id': request.id,
        'user_id': request.user_id,
        'error': error,
        'stage': 'execution',
        'agent': agent.id
    }
)
# Full context for debugging

Last Updated: August 9, 2026