HTTP Request IDs
Last updated July 13, 2024
Table of Contents
HTTP request IDs let you correlate router logs for a given web request against the web dyno logs for that same request.
How it works
The Heroku router generates a unique request ID for every incoming HTTP request that it receives. This unique ID is then passed to your application as an HTTP header called X-Request-ID
.
Alternately, you can specify the X-Request-ID
header when making a request. The value must be between 20 and 200 characters, and consist of ASCII letters, digits, or the characters +
, /
, =
, and -
. Invalid IDs will be ignored and replaced with generated ones.
Your app code can then read this ID and log it or use it for other purposes.
Router logs
These request IDs will also also be visible in the Heroku router logs as request_id
. Here’s an example:
$ heroku logs --ps router
2013-03-10T19:53:10+00:00 heroku[router]: at=info method=GET path=/ host=example-app-1234567890ab.herokuapp.com request_id=f9ed4675f1c53513c61a3b3b4e25b4c0 fwd="215.90.1.17" dyno=web.4 connect=0ms service=61ms status=200 bytes=1382
Example: correlate H13 error against app backtrace
Scenario: your app is reporting occasional H13 errors. The router reports the dyno closed the connection; how do we find the web dyno logs associated with one of these requests? Request IDs can help here, especially on a busy app with lots of data in the logs.
Find the router log line for the H13:
2010-10-06T21:51:37-07:00 heroku[router]: at=error code=H13 desc="Connection closed without response" method=GET path=/ host=example-app-1234567890ab.herokuapp.com request_id=30f14c6c1fc85cba12bfd093aa8f90e3 fwd="215.90.1.17" dyno=web.15 connect=3030ms service=9767ms status=503 bytes=0
Note the request ID, which is 30f14c6c1fc85cba12bfd093aa8f90e3
in this example. Now you can search your logs (using Papertrail or other log search method) for that ID and find the request, logged by the middleware shown in the previous section:
2010-10-06T21:51:37-07:00 heroku[web.15]: request_id=30f14c6c1fc85cba12bfd093aa8f90e3
2010-10-06T21:51:37-07:00 heroku[web.15]: /usr/local/heroku/vendor/gems/excon-0.14.0/lib/excon/ssl_socket.rb:74: [BUG] Segmentation fault
Here we see that this request is causing the dyno to crash due to a bug in the SSL library and/or the Ruby interpreter. Upgrading to a newer version of Ruby might help, or if not, you now have the backtrace that you can use to file a bug with.
Usage with Rails
By default, Rails 3.2 and above will pick up the X-Request-ID
and set it as the requests UUID. To tie this request ID in with your application logs, add this line to your config/environments/production.rb
config.log_tags = [ :uuid ]
On Rails 5.0 and up:
config.log_tags = [ :request_id ]
The values in your logs will then be tagged with the request ID:
2014-02-07T00:58:00.978819+00:00 app[web.1]: [6a7f7b2f-889b-4ae8-b849-db1f635c971c] Started GET "/" for 99.61.71.11 at 2014-02-07 00:58:00 +0000
Here 6a7f7b2f-889b-4ae8-b849-db1f635c971c
is the request ID. You can get more information on tagged logging with UUID in Rails here. Note that if there is not an available X-Request-ID
, then Rails will generate a UUID for you, so if you enable this in development you will see a “request ID” even though one may not be provided in the X-Request-ID
header.
Usage with Ruby Rack middleware
Here is a sample Rack module for logging the request ID.
module Rack::LogRequestID
def initialize(app); @app = app; end
def call(env)
puts "request_id=#{env['HTTP_X_REQUEST_ID']}"
@app.call(env)
end
end
If you are using Rack without Rails, for a more comprehensive Rack middleware, we recommend the Heroku Request ID gem. Do not use this gem if you are using Rails, this gem will break Rails request logging functionality. Using the gem with Rack will provide timing information if you have Rack::Runtime
enabled:
use Rack::Runtime
Usage with Node.js
If you’re using the logfmt Node module >=0.21.0
, the X-Request-Id
header will automatically be output to your logs on every web request as request_id
.
If you prefer not to use logfmt
in your Node app, be aware that Node’s core HTTP module downcases header keys, so the header will be named req.headers['x-request-id']
.
Usage with Django
You can easily use all of the power of request IDs in Django with the excellent django-log-request-id
library. First, you’ll need to install it by adding it to your requirements.txt
file:
django-log-request-id==1.0.0
Next, you need to install the newly available Django middleware into your application’s settings.py
. Make sure that it’s the first specified middleware, or it may not function properly:
MIDDLEWARE_CLASSES = (
'log_request_id.middleware.RequestIDMiddleware',
...
Finally, you need to modify settings.py
to configure Django to properly log outgoing requests to stdout
.
# Support for X-Request-ID
LOG_REQUEST_ID_HEADER = 'HTTP_X_REQUEST_ID'
LOG_REQUESTS = True
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'request_id': {
'()': 'log_request_id.filters.RequestIDFilter'
}
},
'formatters': {
'standard': {
'format': '%(levelname)-8s [%(asctime)s] [%(request_id)s] %(name)s: %(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'filters': ['request_id'],
'formatter': 'standard',
},
},
'loggers': {
'log_request_id.middleware': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
}
Usage with Java
Depending on the framework your application is using, the method of accessing and logging the request ID in Java can vary. This is a simplified example using a Java Servlet filter to access the header and print it to System.out
.
RequestIdLoggingFilter.java
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class RequestIdLoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
final String requestId = ((HttpServletRequest) servletRequest).getHeader("X-Request-ID");
if (requestId != null) {
System.out.println("request_id=" + requestId);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<filter>
<filter-name>RequestIdLoggingFilter</filter-name>
<filter-class>com.example.config.RequestIdLoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RequestIdLoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Usage with PHP
Assuming you’re using the excellent Monolog package, you can simply register a custom processor that adds the contents of the X-Request-Id
HTTP header to the extra
fields array of the record:
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
require('vendor/autoload.php');
$logger = new Logger('my_logger');
$logger->pushHandler(new StreamHandler('php://stderr', Logger::DEBUG));
$logger->pushProcessor(function ($record) {
$record['extra']['request_id'] = isset($_SERVER['HTTP_X_REQUEST_ID']) ? $_SERVER['HTTP_X_REQUEST_ID'] : null;
return $record;
});
$logger->addInfo('Hello World');
The logged message will then contain the request ID in your heroku logs
stream:
2014-11-19T15:10:32.027154+00:00 app[web.1]: [2014-11-19 15:10:31] my_logger.INFO: Hello World [] {"request_id":"a0a86a10-2a3a-4852-ae80-893e5d4e8348"}
For more information, check out the documentation section on Processors, or framework-specific documentation on integrating Monolog Processors using Symfony.
Usage with Scala
Depending on the framework your application is using, the method of accessing and logging the request ID in Scala can vary. This is a simplified example using a Play Framework filter to access the header and log it.
import play.api.mvc._
import play.api.Logger
import scala.concurrent.Future
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object RequestIdLoggingFilter extends Filter {
def apply(nextFilter: (RequestHeader) => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
Logger.info(s"request_id=${requestHeader.headers.get("X-Request-ID").getOrElse("None")}")
nextFilter(requestHeader)
}
}
You can use the filter by declaring a Global object.
object Global extends WithFilters(RequestIdLoggingFilter) {
// ...
}
The logged message will then contain the request ID in your heroku logs
stream:
2014-11-19T16:08:59.427428+00:00 app[web.1]: [info] application - request_id=2bcf9cec-7717-40e3-9bcf-b438b93b7770
Usage with Clojure
Depending on the framework your application is using, the method of accessing and logging the request ID in Clojure can vary. This an example using the Compojure routing library to access the header and log it.
(defroutes request-id-logger
(ANY "*" {:keys [headers params body] :as request}
(println (format "request_id=%s" (get headers "x-request-id")))))
As long as this route is declared before your other routes, it will be executed before the router moves on to match any other routes.
The logged message will then contain the request ID in your heroku logs
stream:
2014-12-20T23:23:56.782823+00:00 app[web.1]: request_id=0e748c63-70ef-4d45-ab97-96510ddcdf5a