Making GraphQL Errors Reproducible: Logging, Tracing, Correlation IDs
AI generated
{ }
type
GraphQL · Logging · Tracing · Correlation IDs · Magento · Observability
Making GraphQL Errors Reproducible:
Logging, Tracing, and Correlation IDs

Intermittent GraphQL errors are hard to reproduce because context is missing. Structured logging, request tracing, and correlation IDs make every error traceable, even days after it occurred, even across multiple resolver chains.

15 min read Correlation ID · Structured Logging · Resolver Tracing · Magento Logs GraphQL · Magento 2 · Observability

1. Why intermittent GraphQL errors are especially hard to debug

An intermittent error occurs but cannot be reproduced. When a user reports that checkout failed, and the error hasn't recurred since, a debugging session begins that usually goes nowhere without context. What was the exact query? Which variables were passed? Was the user logged in? Which resolver threw the exception? Without logging and tracing, all of these remain unanswered questions.

GraphQL makes this problem worse compared to REST because all requests go through the same endpoint. Logs only show POST requests to /graphql, with no information about which operation was executed. Magento logs contain PHP exceptions with stack traces, but not the associated GraphQL query or variables. The result: errors that occur in production cannot be recreated in development because the context that triggered the error in the first place is missing.

2. Correlation IDs: uniquely identifying every request

A correlation ID is a unique identifier that is generated at the start of every request, or sent by the client, and then passed through every logging layer. The pattern: the client either sends an X-Correlation-ID header, or the server generates a UUID when it receives the request and returns it as a response header. All log entries belonging to this request, GraphQL resolvers, database queries, cache accesses, are tagged with this ID.

This makes it possible to find all entries for a specific failed request in a central log system by filtering on the correlation ID. The correlation ID is returned as part of the extensions object in the errors array of a GraphQL response, and the client can show it to the user as a reference number that support can then look up in the log system. Without correlation IDs, every production error is a search for a needle in a haystack.


# GraphQL error response with correlation ID in extensions
# Client sends X-Correlation-ID header or server generates it

# Request header:
# X-Correlation-ID: 550e8400-e29b-41d4-a716-446655440000

# Error response with correlation ID attached to extensions:
{
  "errors": [
    {
      "message": "An error occurred. Please try again.",
      "extensions": {
        "category": "graphql-no-such-entity",
        "correlation_id": "550e8400-e29b-41d4-a716-446655440000"
      }
    }
  ],
  "data": { "product": null }
}

# Support workflow:
# User reports: "Error with reference 550e8400..."
# Support searches logs: grep "550e8400" /var/log/magento/graphql.log
# All resolver calls, DB queries, cache misses for this request visible

3. Structured logging in GraphQL resolvers

Structured logging means writing log entries as JSON objects instead of free-text strings. This lets log aggregators like Elasticsearch, Loki, or CloudWatch Logs filter and aggregate on specific fields. A structured log entry for a GraphQL resolver contains at minimum: timestamp, log level, operation name, resolver path, execution time, and correlation ID. Error entries additionally contain the exception class, the message, and the stack trace.

In Magento, structured logging can be implemented via Monolog, which is already integrated into Magento. Monolog's processor mechanism allows correlation ID and request context to be added automatically to every log entry, without every resolver having to pass that information explicitly. A dedicated GraphQL logger channel with its own log file (var/log/graphql.log) separates GraphQL-specific logs from general Magento logs and makes targeted evaluation easier.

4. Resolver tracing: tracking execution time and path

GraphQL resolvers execute in a tree structure, and performance problems often originate in a branch of the tree that triggers many database queries. Resolver tracing captures the execution timing (start, end, duration) and the path in the query tree for every resolver. The result is a complete profile of resolver execution that immediately shows which resolver took the most time.

The GraphQL standard for tracing is the tracing extension, implemented by Apollo Server and returned in the extensions.tracing field of the GraphQL response. In Magento, resolver tracing is not built in but can be implemented via custom middleware or resolver wrappers. For production systems, tracing is typically switched off (too much overhead and information leakage) and is only enabled on request or in staging environments. OpenTelemetry is the future-proof solution for distributed tracing, connecting resolver timing profiles with the broader request context.

5. Evaluating Magento logs for GraphQL errors

Magento writes GraphQL errors to several log files, depending on the type of error. PHP exceptions that aren't caught by a GraphQL exception handler end up in var/log/exception.log. Resolver-specific errors can appear in var/log/system.log if the resolver explicitly writes to the system logger. In developer mode, the stack trace additionally appears in the extensions object of the GraphQL error response.

A problem with Magento log evaluation: the logs contain no information about which GraphQL query triggered the exception. You see the PHP error's stack trace, but not the query text, the variables, or the user ID. One improvement: in a request plugin, read the GraphQL query and operation name from the body and pass them as context information to the logger before the resolvers run. Then the operation name appears in every log entry for that request.


# GraphQL operation logging: structured log entry pattern
# Written at request start (before resolver execution)

# Log entry format (JSON structured logging via Monolog):
# {
#   "timestamp": "2026-05-09T14:23:11Z",
#   "level": "INFO",
#   "channel": "graphql",
#   "message": "GraphQL request received",
#   "context": {
#     "correlation_id": "550e8400-e29b-41d4-a716-446655440000",
#     "operation_name": "GetCustomerOrders",
#     "operation_type": "query",
#     "customer_id": 42,
#     "ip": "203.0.113.5"
#   }
# }

# Query that triggered the error (also logged):
query GetCustomerOrders($pageSize: Int = 10) {
  customer {
    orders(pageSize: $pageSize) {
      items {
        order_number
        status
        grand_total { value currency }
      }
    }
  }
}

# Variables logged alongside (never log sensitive fields like passwords):
# { "pageSize": 10 }

6. extensions.debug: returning context in the error response

In development environments, it is useful to return debug information directly in the extensions object of the error response, without the developer having to log into the log file. Magento already does this in developer mode: the stack trace appears as extensions.trace. Beyond that, custom debug information can be added: which resolver threw the error, how many database queries were executed, which cache entries were missed.

Important: debug extensions must not be returned in the production environment. Stack traces and internal system information are a security risk because they give attackers information about the internal architecture. The clean implementation: debug extensions are configuration-dependent and only activated when the MAGE_MODE=developer environment variable is set. In production, the extensions object only contains the correlation ID and the error category.

7. Error reproduction: reconstructing query, context, and variables

Fully reproducing a production error requires three sources of information: the exact GraphQL query text (including fragments), the variables that were sent, and the context (user ID, session status, timestamp). If these three pieces of information are logged, any production error can be reproduced in a development environment, simply by replaying the logged request in a debugging tool like Altair or through a replay script.

Logging queries involves a trade-off: queries can be large (with deeply nested selections and fragments) and then generate substantial log volume. A pragmatic approach: always log the operation name and the variables (both are small), and only log the full query text on errors. This reduces log volume in normal operation while still providing the information needed for error reproduction. For persisted queries, where the client sends a query ID instead of the query text, the query ID is sufficient since the text is already known on the server.

8. Logging strategies compared

Different logging approaches offer different searchability, overhead, and implementation effort. The right strategy depends on the infrastructure and the debugging requirements.

Strategy Searchability Overhead Reproducibility
No logging None Zero Not possible
Magento exception.log Stack trace, no query Minimal Context missing
Structured logging + correlation ID Complete Low High
Resolver tracing (always on) Very high High Very high
OpenTelemetry sampling High (sample-based) Moderate High

For most Magento projects, structured logging with a correlation ID is the best balance: low overhead, complete searchability, and high error reproducibility. Resolver tracing is valuable for performance debugging in staging environments, but too costly for continuous production operation. OpenTelemetry sampling is the solution for high-volume systems where even structured logging of every request would generate too much log volume.

9. Common observability gaps in Magento GraphQL projects

The most common gap is the missing operation name in the logs. All GraphQL requests appear as a POST to /graphql, which makes searching logs for specific operations impossible. The fix is simple: read the operation name from the request body and pass it as context information to the logger. This is a one-time implementation in a request plugin or middleware layer that then applies to all GraphQL requests.

A second common gap is missing variable logging on errors. If an error only occurs with certain variable combinations (for example, a specific product ID or a specific promo code), the error cannot be reproduced without the variables. At the same time, variables should not be logged unfiltered: passwords and credit card information must be removed from the log entry. A variable sanitizer that replaces known sensitive fields with placeholders is the right solution.


# Variable sanitization before logging: never log sensitive fields

# Raw variables from request (DO NOT log as-is):
# {
#   "email": "customer@example.com",
#   "password": "secretpass123",
#   "cart_id": "abc123",
#   "payment": { "code": "checkmo", "cc_number": "4111111111111111" }
# }

# Sanitized variables for logging (safe to store):
# {
#   "email": "customer@example.com",
#   "password": "[REDACTED]",
#   "cart_id": "abc123",
#   "payment": { "code": "checkmo", "cc_number": "[REDACTED]" }
# }

# Sensitive field list to always redact:
# password, currentPassword, newPassword, cc_number, cvv, token (auth tokens)

# Correlation ID in GraphQL extension for user-facing error reference:
query CheckoutQuery($cartId: String!) {
  cart(cart_id: $cartId) {
    items { product { name } quantity }
    prices { grand_total { value currency } }
  }
}
# correlation_id: "550e8400-..." returned in extensions on error
# User sees reference number, support looks up in logs

10. Summary

Making GraphQL errors reproducible is an infrastructure problem, not a debugging problem. Without correlation IDs, structured logging, and operation names in the logs, every intermittent production error is guesswork. The implementation is one-time work: a request plugin that logs the operation name, correlation ID, and sanitized variables makes every future error traceable, without every resolver having to implement its own logging logic.

For Magento projects, the most important measures are: set up a dedicated GraphQL logger channel, return correlation IDs as response headers and in error extensions, include operation names in all log entries, and keep variables logged (sanitized) on errors. Resolver tracing and OpenTelemetry are useful extensions for growing systems, but not a prerequisite for basic reproducibility of production errors.

Making GraphQL Errors Reproducible: The Essentials at a Glance

Correlation ID

Unique identifier per request, passed through every logging layer. Return it in error extensions to enable targeted log searches from user reports.

Log operation names

All GraphQL requests appear as POST /graphql. Read the operation name from the body and include it in every log entry, a one-time implementation in the request plugin.

Sanitize variables

Log variables on errors, but replace sensitive fields (password, cc_number) with [REDACTED]. Error logs without variables don't allow reproduction.

Debug only in dev

extensions.debug with stack trace and internal information only in developer mode. In production, extensions only contain the correlation ID and the error category.

11. FAQ: Making GraphQL Errors Reproducible - Logging, Tracing, Correlation IDs

1What is a correlation ID and why do I need one?
Unique identifier per request, passed through every logging layer. Enables filtering all log entries for a specific request in the log aggregator.
2Why do all GraphQL requests appear as POST /graphql?
GraphQL uses a single endpoint. The operation name lives in the body, a request plugin must read it and add it as log context.
3How do I log variables without sensitive data?
A variable sanitizer with a centrally configurable list of sensitive fields (password, cc_number, cvv), replaced with [REDACTED].
4Difference between logging and tracing?
Logging: discrete events. Tracing: the entire execution path over time, including timing profiles. Tracing is more powerful, but has higher overhead.
5Enable resolver tracing in production?
Generally no. Overhead and information leakage. Tracing in staging or via OpenTelemetry sampling is better for production systems.
6How do I return the correlation ID to the user?
In the extensions field of the GraphQL error and as an X-Correlation-ID response header. The frontend shows it as a reference number.
7How do I reconstruct a production error?
Read the operation name, sanitized variables, and user context from logs. Load the query from source code. Run it in Altair with the same variables and auth token.
8What should a GraphQL log entry contain at minimum?
Timestamp, level, correlation ID, operation name, operation type, execution time, user context. On errors: exception class, message, stack trace.
9Difference between exception.log and a dedicated graphql.log?
exception.log contains all PHP exceptions. graphql.log contains only GraphQL-specific information with request context, making targeted evaluation much easier.
10How do I prevent debug information from appearing in production?
Debug extensions only when MAGE_MODE=developer. In production, only the correlation ID and error category go in extensions. The check should happen centrally in the error handler, not in every resolver.