<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: implementing-llm-monitoring-observability
description: >-
  Implement comprehensive monitoring and observability for LLM-powered
  applications including token usage tracking, latency monitoring, cost
  analysis, safety metric collection, and anomaly detection across the
  full inference pipeline.
domain: cybersecurity
subdomain: ai-llm-security
tags:
  - llm-monitoring
  - observability
  - telemetry
  - anomaly-detection
  - cost-tracking
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1498", "T1499"]
  owasp-llm: ["LLM04", "LLM10"]
  mitre-atlas: ["AML.T0048"]
  tools: ["python3", "opentelemetry", "prometheus-client", "structlog"]
---

# Implementing LLM Monitoring & Observability

## Overview

LLM applications require specialized observability beyond traditional APM.
Token consumption, prompt/completion ratios, safety filter triggers, hallucination
rates, and cost per request must be tracked in real time. Anomalies in these
signals can indicate abuse, prompt injection, data exfiltration, or model
degradation before business impact materializes.

## Prerequisites

| Tool / Requirement | Details |
|---|---|
| `python3` | Security tooling |
| `opentelemetry-api` | Security tooling |
| `prometheus-client` | Security tooling |
| `structlog` | Security tooling |
| Access to LLM API gateway or proxy layer | Environment requirement |
| Time-series database (Prometheus, InfluxDB, or cloud equivalent) | Environment requirement |
| Alerting pipeline (PagerDuty, OpsGenie, or webhook) | Environment requirement |

## Key Concepts

- **Token accounting**: Track input/output tokens per request for cost and abuse detection
- **Latency decomposition**: Separate network, queue, and inference latency
- **Safety signal telemetry**: Log guardrail triggers, refusals, and filter activations
- **Semantic drift detection**: Monitor embedding distance between prompts over time
- **Cost attribution**: Map token spend to users, teams, and features

## Workflow

### Step 1: Instrument the Inference Pipeline

Build structured telemetry around every LLM call:

```python
import time
import structlog
from dataclasses import dataclass, field
from typing import Any

logger = structlog.get_logger()


@dataclass
class LLMRequestMetrics:
    request_id: str
    model: str
    input_tokens: int = 0
    output_tokens: int = 0
    latency_ms: float = 0.0
    status: str = "pending"
    safety_flags: list[str] = field(default_factory=list)
    cost_usd: float = 0.0


COST_PER_1K: dict[str, dict[str, float]] = {
    "gpt-4o": {"input": 0.0025, "output": 0.01},
    "gpt-4o-mini": {"input": 0.00015, "output": 0.0006},
    "claude-sonnet-4-20250514": {"input": 0.003, "output": 0.015},
}


def calculate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
    rates = COST_PER_1K.get(model, {"input": 0.001, "output": 0.002})
    return round(
        (input_tokens / 1000) * rates["input"]
        + (output_tokens / 1000) * rates["output"],
        6,
    )


def track_llm_call(
    model: str, request_id: str, input_tokens: int, output_tokens: int,
    latency_ms: float, safety_flags: list[str] | None = None,
) -> dict[str, Any]:
    cost = calculate_cost(model, input_tokens, output_tokens)
    metrics = LLMRequestMetrics(
        request_id=request_id,
        model=model,
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        latency_ms=latency_ms,
        status="completed",
        safety_flags=safety_flags or [],
        cost_usd=cost,
    )
    logger.info(
        "llm_request_completed",
        request_id=metrics.request_id,
        model=metrics.model,
        tokens_in=metrics.input_tokens,
        tokens_out=metrics.output_tokens,
        latency_ms=metrics.latency_ms,
        cost_usd=metrics.cost_usd,
        safety_flags=metrics.safety_flags,
    )
    return {
        "request_id": metrics.request_id,
        "model": metrics.model,
        "input_tokens": metrics.input_tokens,
        "output_tokens": metrics.output_tokens,
        "latency_ms": metrics.latency_ms,
        "cost_usd": metrics.cost_usd,
        "safety_flags": metrics.safety_flags,
    }
```

### Step 2: Prometheus Metrics Export

Expose key metrics for time-series collection and alerting:

```python
from prometheus_client import Counter, Histogram, Gauge

LLM_REQUESTS = Counter(
    "llm_requests_total", "Total LLM API requests", ["model", "status"]
)
LLM_TOKENS_IN = Counter(
    "llm_input_tokens_total", "Total input tokens", ["model"]
)
LLM_TOKENS_OUT = Counter(
    "llm_output_tokens_total", "Total output tokens", ["model"]
)
LLM_LATENCY = Histogram(
    "llm_request_duration_seconds", "LLM request latency",
    ["model"], buckets=[0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0],
)
LLM_COST = Counter("llm_cost_usd_total", "Cumulative cost in USD", ["model"])
LLM_SAFETY_TRIGGERS = Counter(
    "llm_safety_triggers_total", "Safety filter activations", ["model", "flag"]
)
LLM_ACTIVE = Gauge("llm_active_requests", "Currently in-flight requests", ["model"])


def record_prometheus_metrics(metrics: dict) -> None:
    model = metrics["model"]
    LLM_REQUESTS.labels(model=model, status="ok").inc()
    LLM_TOKENS_IN.labels(model=model).inc(metrics["input_tokens"])
    LLM_TOKENS_OUT.labels(model=model).inc(metrics["output_tokens"])
    LLM_LATENCY.labels(model=model).observe(metrics["latency_ms"] / 1000)
    LLM_COST.labels(model=model).inc(metrics["cost_usd"])
    for flag in metrics.get("safety_flags", []):
        LLM_SAFETY_TRIGGERS.labels(model=model, flag=flag).inc()
```

### Step 3: Anomaly Detection on Token Usage

Detect abuse patterns through statistical anomaly detection:

```python
import statistics


class TokenAnomalyDetector:
    def __init__(self, window_size: int = 100, z_threshold: float = 3.0) -> None:
        self.window: list[float] = []
        self.window_size = window_size
        self.z_threshold = z_threshold

    def ingest(self, token_count: int) -> dict[str, Any]:
        self.window.append(float(token_count))
        if len(self.window) > self.window_size:
            self.window = self.window[-self.window_size:]
        if len(self.window) < 10:
            return {"anomaly": False, "reason": "insufficient_data"}
        mean = statistics.mean(self.window)
        stdev = statistics.stdev(self.window)
        if stdev == 0:
            return {"anomaly": False, "z_score": 0.0}
        z_score = (token_count - mean) / stdev
        is_anomaly = abs(z_score) > self.z_threshold
        return {
            "anomaly": is_anomaly,
            "z_score": round(z_score, 3),
            "mean": round(mean, 1),
            "stdev": round(stdev, 1),
            "severity": "HIGH" if is_anomaly else "NONE",
        }
```

### Step 4: Alerting Rules

Define alert conditions for operational and security events:

```yaml
# prometheus-alerts.yml
groups:
  - name: llm_security
    rules:
      - alert: HighTokenBurnRate
        expr: rate(llm_input_tokens_total[5m]) > 50000
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Abnormal input token rate — possible abuse"
      - alert: SafetyFilterSpike
        expr: rate(llm_safety_triggers_total[5m]) > 10
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Safety filter triggering at elevated rate"
      - alert: HighLatencyP99
        expr: histogram_quantile(0.99, llm_request_duration_seconds_bucket) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "LLM P99 latency exceeds 10s"
```

## Detection

```yaml
title: Llm Monitoring Observability Detection
id: 2e6a1766-c2e4-4de8-8253-d9e1c9e42f78
status: experimental
description: Detects suspicious activity related to implementing llm monitoring observability techniques in ai llm security context
logsource:
  category: application
  product: webserver
detection:
  selection:
    EventType: error
  condition: selection
level: medium
tags:
  - attack.t1498
  - attack.t1499
  - attack.initial_access
falsepositives:
  - Automated AI model training and inference pipeline operations
```


**Detection Opportunities**

| Indicator | Source | Detection Logic |
|---|---|---|
| Llm Monitoring Observability Detection | webserver/application | Sigma rule (medium) |
| ATT&CK Coverage | MITRE ATT&CK | T1498, T1499 |

## Verification

- [ ] Token accounting matches billing provider totals within 1%
- [ ] Latency histograms populated across all percentile buckets
- [ ] Safety trigger counters increment on guardrail activation
- [ ] Anomaly detector flags synthetic abuse patterns in test
- [ ] Alerting fires within 2 minutes of threshold breach
- [ ] Cost attribution accurate per user/team

## References

- [OpenTelemetry Semantic Conventions for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/)
- [OWASP LLM04 — Model Denial of Service](https://genai.owasp.org/llmrisk/llm04-model-denial-of-service/)
- [NIST AI 100-1 — AI Risk Management Framework](https://www.nist.gov/artificial-intelligence/executive-order-safe-secure-and-trustworthy-artificial-intelligence)
- [Prometheus Best Practices — Instrumentation](https://prometheus.io/docs/practices/instrumentation/)
