Available since Router 0.118.0
stdout, but logging to a file is also supported, which is recommended for high-load scenarios. However, only one logging mode—either stdout or file—can be enabled at any given time. The logs include a predefined set of fields that help identify HTTP requests. Additionally, custom fields can be configured to capture more detailed information from the GraphQL requests.
Logging to stdout
The following configuration enables logging tostdout:
Logging to file
For environments with high traffic, logging to a file is recommended. Below is an example configuration for logging to a file:Setting file permissions
By default, the file will be created with permissions0640. You can change this by setting the mode field:
The
mode uses Linux-style octal permissions. For example, 0644 means -rw-r—r—:- The first digit (
6) is for the owner (read + write) - The second digit (
4) is for the group (read only) - The third digit (
4) is for others (read only)
Log level
You can control the verbosity of access logs. The default is info, and based on the log level, any log entry that would be printed above the set log level would be excluded. For example, if “error” was the level set, “info”, “debug” and “warn” log entries would be excluded.Filter access logs
Setaccess_logs.router.filter to a boolean template expression. An entry is emitted when its filter returns true and its log level is enabled. An omitted or empty filter includes every entry. Use the string "false" to suppress all router access log entries except panic entries.
Filtering only controls access log emission. Request execution, responses, tracing, metrics, and panic recovery continue normally. Panic entries bypass the filter and are always logged.
rand() is available in access log filters and returns a fresh number greater than or equal to zero and less than one for each call. Sampling is independent for each log entry. Use request.trace.sampled instead to follow the trace sampling decision.
The environment variable is ACCESS_LOGS_ROUTER_FILTER.
Expression sources
Filters use the samerequest and response objects as router log field expressions. Sources do not need to be added to fields to be used by a filter. These include request headers, authentication claims and scopes, operation details and variables, bodies, and cache results.
Router filters run after the request completes, when the response and operation results are available. WebSocket logs retain the expression context of the HTTP upgrade, so HTTP operation details are unavailable there.
Referencing bodies, operation variables, or operation SHA-256 hashes in a filter enables the same data collection as referencing them in a field expression. This work occurs even if the filter ultimately suppresses the entry.
GraphQL errors can have HTTP status 200. Use request.error != nil, or a graphql_error_codes context field read through the log map, when filtering for errors instead of relying only on log.status.
Filter by output fields
Filters additionally provide alog map containing the built-in and configured custom fields for the entry. Field names match the output keys, including snake case. For example:
log.status,log.method,log.path,log.query,log.ip,log.user_agent, andlog.latency.log.config_version,log.log_type, and any availablelog.request_id,log.trace_id,log.feature_flag, orlog.batched_request_operation_id.- Custom keys from
request_header,context_field, static defaults, and expressions. Their configured defaults and omission rules are applied before the filter runs.
hostname and pid, are not part of the map. Logger-generated metadata such as the output timestamp, caller, and stack trace is not part of the map either. The log map is only available in filters, not in field expressions. It is only built when the filter references log.
The map preserves value types. Durations such as log.latency are time.Duration values, so compare them with duration('100ms'). They are converted to seconds only when the log is serialized. IP anonymization and query-parameter exclusions are already applied to log.ip and log.query.
Missing keys evaluate to nil. Optional fields are common: boolean context fields such as request_error are omitted when false. Compare optional booleans explicitly with log.key == true or provide a default with (log.key ?? false). Do not use a missing key directly as a boolean, because the result depends on its position. With ! or as the left operand of && or ||, it is an evaluation error and the entry is kept. Alone or as the last operand, it evaluates to false and the entry is dropped without a diagnostic. For keys containing punctuation, use bracket notation such as log['tenant.id']. If keys are repeated, the last field value is used.
Validation and evaluation errors
Filters are compiled when the router starts and on every router configuration reload. Invalid syntax, unknown typed context properties, and statically non-boolean results fail startup. A configuration reload stops the running router before the new configuration is built, so an invalid filter stops the router. Validate filter changes before rolling them out. A filter is only compiled when its access log is enabled. If evaluation fails because of request data, such as a non-numeric header passed toint(), the entry is preserved subject to the configured log level. A diagnostic is logged at debug level, so it is only visible with access_logs.level: debug. Guard optional values or provide defaults to avoid repeated evaluation errors.
Add stack traces
You have the option of adding stack traces to all error logs. Note that by default even if this feature is disabled panics will contain stack traces.As of router 0.258.3 any requests that caused an error are logged as “error” on the access logs. Previously all non-panic access logs were marked as “info” logs.
Default Log Fields
The default log fields provide essential details about each HTTP request. These fields include:- hostname: The hostname of the machine handling the request.
- pid: The process ID of the router instance.
- request_id: The request ID generated by the router instance.
- trace_id: The Trace ID propagated or generated by the router instance.
- status: The HTTP status code of the request.
-
method: The HTTP method used (e.g.,
GET,POST). -
path: The request path (e.g.,
/graphql). -
query: The query string included in the request URL (e.g.,
/?param=1). Certain parameters can be skipped using the ignore_query_params_list option. - ip: The client’s IP address (redacted by default).
-
user_agent: The
User-Agentheader value provided by the client. - config_version: The version of the router configuration used to handle the request.
- latency: The time taken to process the request, in float seconds.
- log_type: Identifies the type of log (useful for distinguishing access logs from other log types).
Example Log Entry
An example of a log entry is shown below:Filtering Query Parameters
Thequery field above by default will print the raw query string if present. However, this can potentially include sensitive information, especially if the GraphQL request was made over GET. Users can ignore specific query parameters using the following configuration, which will skip printing the “extensions” query parameter:
ignore_query_params_list is specified, “variables” is ignored by default.
Custom Fields
In addition to the default fields, you can extend the log with custom fields. These can include values extracted from request headers or additional information generated after the GraphQL operation has been processed. Let’s take the following example:- service: This field logs the name of the service making the request, extracted from the request header “x-service”. It helps identify which specific service is making the API call.
- operationName: This field logs the name of the GraphQL operation being performed. It is extracted from the context of the request and indicates which query or mutation is being executed.
- operationSha256: This field logs the SHA-256 hash of the original GraphQL operation, serving as a unique identifier for the operation’s structure. It helps to identify operations in a secure and consistent way.
-
requestError: This field is only present when an error has been encountered with the request, and will be
trueif so. - responseErrorMessage: This field logs any error message returned in the response. It captures detailed information about what went wrong during the execution of the operation.
- serviceNames: This field logs the names of all services involved in processing the operation. It can be useful for tracking dependencies or microservices that are part of the request’s execution.
- operationHash: This field logs a unique hash generated for the normalized operation. It serves as an identifier for the specific instance of the operation being executed, useful for tracing or debugging.
- errorCodes: This field logs any GraphQL error codes returned in the response. These codes help diagnose issues within the GraphQL schema or logic.
- operationType: This field logs the type of GraphQL operation being executed, such as a query, mutation, or subscription. It helps classify the request.
- persistentOperationSha256: This field logs the SHA-256 hash of the persisted GraphQL operation. Persisted operations are pre-saved on the server for efficiency, and this hash allows tracking them.
- errorServiceNames: This field logs the names of services that were involved when the operation resulted in an error. It helps identify which microservices or external APIs contributed to the issue.
- operationParsingTime: This field logs the amount of time (float seconds) spent parsing the GraphQL operation. It measures how long it took to interpret the operation’s syntax.
- operationPlanningTime: This field logs the time (float seconds) taken for planning the execution of the GraphQL operation, including resolving dependencies or building execution strategies.
- operationNormalizationTime: This field logs the time (float seconds) spent normalizing the GraphQL operation, which involves standardizing the operation to ensure consistency in processing.
- operationValidationTime: This field logs the time (float seconds) spent validating the GraphQL operation, ensuring that the operation is correct and adheres to the GraphQL schema and rules before execution.
Error-Specific Log Fields
In addition to the standard and custom fields typically present in a log, certain fields are included only in error scenarios to capture details about issues that occurred during request processing. These fields are useful for diagnosing errors in GraphQL operations and understanding the root causes. Here are the fields that are specifically added in error cases:- graphql_error_service_names: Lists the names of the services involved in the GraphQL request where an error occurred. This helps in pinpointing which microservice(s) or backend service(s) contributed to the error.
- graphql_error_codes: Provides error codes associated with the GraphQL operation. These codes may correspond to specific validation, execution, or schema-related errors that occurred during processing.
- response_error_message: Contains the actual error message returned in the response. This message can be used to better understand what went wrong and why the request failed.
-
requestError: This field is only present when an error has been encountered with the request, and will be
trueif so. This can better help clients filter their logs (ifrequestError: true)to find error cases.
Expression Fields
Available since Router 0.186.0
request.erroris present, the expression will return the error to the access logger to be printed. However if the request.erroris nil, the expression will instead will return the value of the header x-header .
The response type of the expression is validated upon startup. You should ensure that the expression returns a value. Additionally the following are illegal return types
- Channels
- Functions
Logging operation complexity
Router access-log expressions can record the operation complexity values that are also attached to validation traces. Configuresecurity.complexity_limits first. Use measure mode to calculate the values without rejecting operations.
Subgraph Access Log Expressions
Available since Router 0.214.1
subgraph expression context object.
Subgraph response headers are available through subgraph.response.header.Get('Header-Name'), and the start of the subgraph latency measurement is available as subgraph.request.startTime (Unix epoch in milliseconds). Combined with the date function, these let you derive timing values from subgraph responses.
Guard header access with a conditional so the expression still resolves when a response does not carry the header. An absent header makes float(...) or date(...) fail to resolve, in which case the field falls back to its configured default (or is omitted).
Default value
When extracting values from a header, it can be beneficial to ensure a default value is set. This can be achieved by defining the value as follows:Example Log Entry
Structured log output
In development, log lines are formatted for readability using pretty printing. In production mode, all logs are output as newline-separated JSON values.Subgraph Access Logs
Available since Router 0.146.0 In addition to router logs, users can enablesubgraph access logs as well, to get detailed logging of requests made to subgraphs. These logs are useful for tracing and debugging interactions between the router and its connected subgraphs. The configuration allows for custom fields to be included in the logs, providing additional context about requests and responses.
Configuration
To enable Subgraph Access Logs, use the following configuration structure:Filter subgraph access logs
Subgraph filtering works like router access log filtering, with the same expression syntax,log field access, sampling, defaults, and error behavior. Configure access_logs.subgraphs.filter or the ACCESS_LOGS_SUBGRAPH_FILTER environment variable. It operates independently of access_logs.router.filter.
This example includes failed fetches, HTTP responses with status 500 or higher, fetches slower than 100 milliseconds, and a random sample of 40% of the remaining fetches:
request describes the incoming client request, while subgraph describes the current fetch. Use subgraph.request.error for fetch errors. The log map includes log.subgraph_name, log.subgraph_id, log.url, and configured custom fields. Referencing client timings or response bodies enables their collection without adding them to the output fields.
Filters also run for response cache hits. Use subgraph.response.cache.status == 'hit' to select them. A cache hit without an outbound HTTP request has no log.url, log.method, log.path, log.query, log.ip, or log.user_agent; guard these optional fields with a nil check. The completed router response is not available during a subgraph fetch.
Log Format
Subgraph Access Logs are structured in JSON format. An example log entry is shown below:Subgraph Specific Log Fields
There are certain log fields that are only added for subgraph access logs. Those include:- subgraph_name: The name of the subgraph
- subgraph_id: The subgraph ID, as configured with the router
- url: The URL of the subgraph
Configuration
Let’s take the following example:- service: This field logs the name of the service making the request, extracted from the request header “x-service”. It helps identify which specific service is making the API call.
- my_response_header: This field logs a header from the subgraph response, extracted from the response header “x-my-response”.
- operationName: This field logs the name of the GraphQL operation being performed. It is extracted from the context of the request and indicates which query or mutation is being executed.
- operationSha256: This field logs the SHA-256 hash of the original GraphQL operation, serving as a unique identifier for the operation’s structure. It helps to identify operations in a secure and consistent way.
- serviceNames: This field logs the names of all services involved in processing the operation. It can be useful for tracking dependencies or microservices that are part of the request’s execution.
- operationHash: This field logs a unique hash generated for the normalized operation. It serves as an identifier for the specific instance of the operation being executed, useful for tracing or debugging.
- operationType: This field logs the type of GraphQL operation being executed, such as a query, mutation, or subscription. It helps classify the request.
- persistentOperationSha256: This field logs the SHA-256 hash of the persisted GraphQL operation. Persisted operations are pre-saved on the server for efficiency, and this hash allows tracking them.
- operationParsingTime: This field logs the amount of time (float seconds) spent parsing the GraphQL operation. It measures how long it took to interpret the operation’s syntax.
- operationPlanningTime: This field logs the time (float seconds) taken for planning the execution of the GraphQL operation, including resolving dependencies or building execution strategies.
- operationNormalizationTime: This field logs the time (float seconds) spent normalizing the GraphQL operation, which involves standardizing the operation to ensure consistency in processing.
- operationValidationTime: This field logs the time (float seconds) spent validating the GraphQL operation, ensuring that the operation is correct and adheres to the GraphQL schema and rules before execution.
-
requestError: This field is only present when an error has been encountered with the request to the subgraph, and will be
trueif so. - responseErrorMessage: This field logs any error message returned in the response from the subgraph. It captures detailed information about what went wrong during the execution of the operation.
Limitations
The following context fields available in router access logs are not available in Subgraph Access Logs:-
graphql_error_codes -
graphql_error_service_names
Conclusion
By configuring access logs, you can gather vital data on the traffic processed by your router. This helps in monitoring performance, debugging issues, and understanding request patterns. Whether logging tostdout or to a file, the logs provide flexibility with both default and custom fields to suit your specific needs.