Monitoring Production Incidents and Failures

Ma Mahalakshmi V Updated 19 Sep 2026
7 min read ·Lesson 198 of 224

Why "It Works" Is Not Enough Information

Once an OpenAI SDK application is deployed and handling real traffic, the question changes from "does it work" to "is it working right now, for everyone, and will I know the moment it stops." Monitoring is the practice of continuously collecting signals from a running system so that failures are detected — ideally before a user reports them — and so that when something does go wrong, there is enough information to diagnose it quickly rather than guessing.

This lesson focuses on what is specifically worth monitoring in an OpenAI SDK application, and on the logging discipline that makes an actual incident traceable after the fact. It builds on Lesson 1's introduction of structured logging in place of print().

What to Monitor

Error rate, broken down by error type. Not every failure is the same kind of problem: a spike in HTTP 429 responses from the OpenAI API means you are being rate-limited (connect this to Unit 12, Lesson 3's rate-limiting logic and Lesson 8's shared-rate-limit concern for horizontally scaled deployments); a spike in 500-level errors from the OpenAI API itself suggests an outage on OpenAI's side, which no amount of retrying on your end will fix; a spike in errors that are entirely your own application's exceptions (a KeyError, a validation failure) points at a bug in your code, not a dependency problem. Lumping all of these into one "error count" metric hides which of three very different responses is actually needed.

Latency, as percentiles, not just an average. An average latency of 800ms can hide the fact that 5% of your requests take 15 seconds — those users are having a genuinely bad experience that an average completely conceals. Tracking p50, p95, and p99 latency separately shows both the typical experience and the tail of it.

Token usage and cost. Because OpenAI API usage is billed by tokens, a sudden increase in token consumption — whether from a bug that causes unnecessarily long prompts, a runaway retry loop, or a genuine traffic increase — has a direct financial consequence. Tracking token usage as a first-class metric, not something you only discover by checking a billing dashboard days later, lets you catch a cost anomaly while it is still small.

Queue depth and worker throughput, for the architecture from Lessons 6 and 7. A queue depth that grows steadily over time, rather than staying roughly flat, means workers are falling behind incoming demand — a leading indicator of user-visible delay before any user has actually complained yet.

Structured Logging With Correlation IDs

A single user request in a production system often touches multiple log lines — a request received, an OpenAI API call made, a retry attempted, a response returned — potentially interleaved in the log stream with lines from other concurrent requests being handled by the same or other replicas. Without something tying those lines together, reconstructing what happened for one specific failing request is close to impossible once there is any real traffic volume.

The fix is a correlation ID (also called a request ID or trace ID): a unique identifier generated once per incoming request and included in every log line produced while handling it.

import logging
import uuid
import contextvars

request_id_var: contextvars.ContextVar[str] = contextvars.ContextVar(
    "request_id", default="-"
)


class RequestIdFilter(logging.Filter):
    def filter(self, record: logging.LogRecord) -> bool:
        record.request_id = request_id_var.get()
        return True


def configure_logging() -> None:
    handler = logging.StreamHandler()
    handler.addFilter(RequestIdFilter())
    handler.setFormatter(
        logging.Formatter(
            "%(asctime)s %(levelname)s [%(request_id)s] %(name)s %(message)s"
        )
    )
    root = logging.getLogger()
    root.addHandler(handler)
    root.setLevel(logging.INFO)


def new_request_id() -> str:
    request_id = str(uuid.uuid4())[:8]
    request_id_var.set(request_id)
    return request_id

contextvars.ContextVar is what makes this work correctly under concurrency: it holds a value that is specific to the current logical task (an async request handler, in most modern Python web frameworks) rather than a single global variable shared across every concurrent request, which would let one request's ID bleed into another's log lines under load. new_request_id() is called once, at the very start of handling each incoming request — typically in middleware — and every subsequent logger.info(...) call anywhere in that request's code path automatically picks up the same id through RequestIdFilter, without every function needing to explicitly pass the id down through every layer of the call stack.

import time

logger = logging.getLogger("myapp.requests")


def handle_summarize_request(client, model: str, document_text: str) -> str:
    start = time.monotonic()
    logger.info("summarize started doc_length=%d", len(document_text))
    try:
        response = client.responses.create(
            model=model, input=f"Summarize:\n{document_text}"
        )
    except Exception:
        elapsed_ms = (time.monotonic() - start) * 1000
        logger.exception("summarize failed after %.0fms", elapsed_ms)
        raise

    elapsed_ms = (time.monotonic() - start) * 1000
    logger.info("summarize completed elapsed_ms=%.0f", elapsed_ms)
    return response.output_text

Every log line in handle_summarize_request will carry the same request_id once configure_logging and new_request_id are wired into the request pipeline, which means that grepping (or, more realistically, querying a log-aggregation platform) for a single request id retrieves the complete story of that one request — start, any failure, and completion — even in a system handling thousands of concurrent requests across multiple replicas.

Classifying and Handling OpenAI API Errors

Not all exceptions from the OpenAI SDK should be treated identically for monitoring purposes. A useful pattern is to classify errors into categories your monitoring and alerting can distinguish between:

from enum import Enum


class ErrorCategory(str, Enum):
    RATE_LIMITED = "rate_limited"
    UPSTREAM_ERROR = "upstream_error"      # OpenAI-side 5xx
    CLIENT_ERROR = "client_error"          # our request was malformed (4xx, not 429)
    UNKNOWN = "unknown"


def classify_openai_error(status_code: int | None) -> ErrorCategory:
    if status_code == 429:
        return ErrorCategory.RATE_LIMITED
    if status_code is not None and 500 <= status_code < 600:
        return ErrorCategory.UPSTREAM_ERROR
    if status_code is not None and 400 <= status_code < 500:
        return ErrorCategory.CLIENT_ERROR
    return ErrorCategory.UNKNOWN


def test_classifies_rate_limit_and_upstream_errors() -> None:
    assert classify_openai_error(429) == ErrorCategory.RATE_LIMITED
    assert classify_openai_error(503) == ErrorCategory.UPSTREAM_ERROR
    assert classify_openai_error(400) == ErrorCategory.CLIENT_ERROR
    assert classify_openai_error(None) == ErrorCategory.UNKNOWN
    print("PASS: error classification maps status codes to the right category")


test_classifies_rate_limit_and_upstream_errors()

Logging (and alerting on) the category, not just "an error occurred," is what lets a human glance at a dashboard and immediately know what kind of response is warranted: a burst of RATE_LIMITED errors points back at the rate-limiting and scaling discussion in Unit 12 and Lesson 8 of this unit; a burst of UPSTREAM_ERROR means checking OpenAI's status page, not your own code; and any CLIENT_ERROR volume above baseline suggests a bug in how your application is constructing requests, worth investigating directly.

Alerting Without Alert Fatigue

An alert that fires too often for conditions that do not actually require action trains whoever receives it to ignore it — which means the one time it fires for something that genuinely matters, it gets ignored along with all the noise. Effective alerting thresholds are set based on what actually requires a human response, not simply "any error at all":

  • Alert on a sustained elevated error rate over a window (for example, error rate above 5% for five consecutive minutes), not on any single error, which is expected to happen occasionally even in a healthy system.
  • Alert on p99 latency crossing a threshold that reflects real user impact, not on p50 latency, which is far more sensitive to normal variation.
  • Alert on queue depth that is both high and growing, not merely nonzero — a queue with a few items that are being processed steadily is normal; one that keeps growing is not.

Common Mistakes

Treating every exception identically in logs and alerts. Without classification, a dashboard showing "47 errors in the last hour" gives no indication of whether that means "OpenAI had a brief outage" or "we shipped a bug that breaks every request" — two situations with completely different responses.

Logging without a correlation id. When an incident report says "a user got an error around 2:15 PM," reconstructing exactly what happened from an unstructured log stream with no way to isolate that one request's lines is far slower than it needs to be, often too slow to matter by the time you find it.

Setting alert thresholds so sensitive that they fire on normal variation. This produces alert fatigue, where genuine, actionable alerts get the same "probably nothing" reaction as the noise around them, delaying real incident response.

Best Practices

Attach a correlation id to every log line for a given request, generated once per request and propagated automatically through a context variable, so any single request's full history can be reconstructed after the fact.

Classify failures by cause, distinguishing rate limiting, upstream provider errors, and your own application's bugs, so monitoring dashboards and alerts point directly at the appropriate response instead of an undifferentiated error count.

Track token usage and cost as an operational metric, not only a billing-dashboard afterthought, so a cost anomaly is caught while it is still small rather than discovered at the end of a billing cycle.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Monitoring Production Incidents and Failures and get answers drawn from it.

Signed-in readers only.