In part 1 we looked at how to quickly set up and configure a Monolog Processor to attach a request ID to our logs. In part 2, we'll implement our own processor and look at what else we can use the request ID for.
The goal here is that for a single request, we need the same request ID to be available throughout the application. We can do this in Craft by creating a Module, or updating one if you already have one. For this post, I'm assuming the module class is Module
in the namespace modules\sitemodule
.
Building our own processor
To start, we'll need to install the ramsey/uuid
package so we can generate UUIDs instead of random bytes.
Next we'll add our own processor to modules\site-module\RequestIdProcessor.php
When this class is instantiated we generate a new UUID. __invoke
is called for each log message in the queue and attaches the request ID. Previously, the UidProcessor
added the request ID to the "extra"
property, but since yii2-psr-log-target
is already using a "context"
property, we add it to that so we don't pollute our logs.
In modules/site-module/Module.php
, we can add an instance property which would store a reference to the RequestIdProcessor. RequestIdProcessor won't instantiated in the Module class however. We'll see why shortly.
Finally, in config/app.php
we tie it all together:
Because we want to access the request ID during execution of the request, but outside of the logging, we need to keep a reference to it. However, config/app.php
is loaded before any modules or plugins are instantiated, so we need to tell Craft to set the requestIdProcessor
property during instantiation.
Now instead of a short random string, we're logging a UUID.
[2019-06-14 05:13:22] website.INFO: Message A {"request_id":"38249a5d-bdd1-4bed-8bb7-07d0008f9c2e"} []
[2019-06-14 05:13:22] website.INFO: Message B {"request_id":"38249a5d-bdd1-4bed-8bb7-07d0008f9c2e"} []
[2019-06-14 05:13:22] website.INFO: Message C {"request_id":"38249a5d-bdd1-4bed-8bb7-07d0008f9c2e"} []
Forwarding the request ID
When your site starts to grow, you might find yourself splitting out services into their own infrastructure. When these services start talking to each other, it becomes more difficult to trace user actions through the system as a whole. Even with request IDs attached to log messages, you would still be limited to focusing on individual services, instead of correlating across the entire system.
One solution is to set the "X-Request-Id"
HTTP header (assuming we're using HTTP) on all outgoing requests to other services, and in those services, retrieve the ID from the header, and if its set, instead of genearting a new ID, use the forwarded one.
In modules\site-module\RequestIdProcessor.php
update the constructor to check for the "X-Request-Id"
header:
Any requests coming in with that header set will automatically set the current request ID to the headers value.
Test it using Postman or curl:
Your logs should print something like this:
[2019-06-14 05:24:22] website.INFO: Message A {"request_id":"my-super-unique-id"} []
[2019-06-14 05:24:22] website.INFO: Message B {"request_id":"my-super-unique-id"} []
[2019-06-14 05:24:22] website.INFO: Message C {"request_id":"my-super-unique-id"} []
Let's test forwarding the ID on to other services. If we had some code which talks to another web service and we're using Guzzle, we can add a Guzzle middleware to automatically attach the request ID to all requests made by that client:
With the middleware in place, we can safely assume all calls with that client will have the correct request ID attached:
Running this, we can see Postman Echo replies with the request ID we sent it:
[2019-06-14 05:30:27] website.INFO: Response: faff6104-a9c1-43b9-ab62-962ef4122b2d {"request_id":"faff6104-a9c1-43b9-ab62-962ef4122b2d"} []
And pass on an ID:
[2019-06-14 05:30:57] website.INFO: Response: my-super-unique-id {"request_id":"my-super-unique-id"} []
User support
When there's a system error, it can be useful to pass the user their request ID and hopefully they would let you know, and send the ID along too.
A quick solution would be to create a Craft variable and use it on your error page, or if you're running Craft in headless mode, you can include the ID in your error responses.