In our Rails app, installing the Honeycomb beeline magically gave us traces of all HTTP requests, with dozens of useful fields like ‘request.path’ and ‘response.status_code’. I really wanted the request verb (GET or POST) and I couldn’t find that. (It’s there, in ‘request.method’, I missed it. But still, it’s useful to know how to a Rack middleware so here’s the post.)
TL;DR, here’s all the code:
# config/initializers/honeycomb_custom.rb
class HoneycombCustom
def initialize(app)
@app = app
end
def call(env)
Honeycomb.add_field("request.verb", env["REQUEST_METHOD"])
@app.call(env)
end
end
Rails.application.config.middleware.use HoneycombCustom
Rack makes a stack of tiny “apps” that all get to see a request and response.
Rails apps use Rack to pass requests through layer upon layer of middleware on their way in and out. See how many in your project with rails middleware
. Ours looks like:
use Webpacker::DevServerProxy
... ten more...
use Honeycomb::Rails::Middleware
... twelve more...
use HoneycombCustom
... another one ...
run Sixmilebridge::Application.routes
At the bottom of that pile of middleware is the real app. Requests start at the top, work their way down, and finally get handled by Application.routes
.
Rack sets these up by telling each one about the next app down.
At initialization, Rack builds up a stack of middleware, starting at the bottom. Each higher one receives the next one down as a parameter to its initialize
method, and saves it.

Rack sends each through the “call” method.
Then as requests come in, Rack passes them in to the top of the stack with .call
, and each middleware has the job of:

- optionally do something with the request
- pass it down to the next app’s
call
method - optionally do something with the response
- return the response.
Technically, a middleware can choose to respond to the request instead of passing it on. But I’m not trying to interfere, here. My middleware calls Honeycomb.add_field
with the extra information I want to add to my trace, then lets the response from the next app down be returned:
def call(env)
Honeycomb.add_field("request.verb", env["REQUEST_METHOD"])
@app.call(env)
end
Give the middleware to Rails in its config.
We supplied our middleware in a new file in the config/initializers
directory in our Rails app. At the bottom of the file, we tell Rails to use
it. We pass it the class as a whole, not an instance; it’ll need to instantiate it while constructing the stack.
Rails.application.config.middleware.use HoneycombCustom
Order matters sometimes
After adding that file, we ran rails middleware
and verified that requests will go through Honeycomb’s middleware before ours (Honeycomb::Rails::Middleware is higher in the stack). That means the Honeycomb trace will be initialized before my code adds a field to it.
use Webpacker::DevServerProxy
... ten more...
use Honeycomb::Rails::Middleware
... twelve more...
use HoneycombCustom
... another one ...
run Sixmilebridge::Application.routes
That’s it. Rack middleware makes it possible to do something with every request and response.
The magic of Honeycomb for me is that I can see that the world is different because my app exists; I have external evidence that requests were served, and details of how. With this tiny middleware, I can get evidence of particular things I care about.
Now you, too, know how to do another thing, and you can make your app influence the world in tiny new ways.
Bonus hint: how did I find where the verb was in the env
? By poking around in the REPL. I stuck a call to byebug
(like literally a line that says “byebug”) right where I wanted to access env
. At the next request, Rails turned into a console. I typed stuff until I found what I wanted, then put that expression in the code and took out the byebug.