Hey guys,
Today I'll share with you a tip that will skyrocket your production debugging workflow ๐
We're going to generate a unique ID for each request, which we can use the request ID to search for every related log of the given request ID.
Thus, if any issue happens, we can try to find the request ID (or customers give us), and then jump immediately to the logs and do the surgery ๐.
Pretty handy, isn't it? ๐ช
Let's jump to the topic & the implementation.
Requirement
You'll need Laravel 11 because we are going to use the Context
feature.
If you haven't learned about Context
, visit this page of mine:
Why Context?
Laravel's "context" capabilities enable you to capture, retrieve, and share information throughout requests, jobs, and commands executing within your application. This captured information is also included in logs written by your application, giving you deeper insight into the surrounding code execution history that occurred before a log entry was written and allowing you to trace execution flows throughout a distributed system.
Read more at: Laravel Context
In short terms:
So if a context presents in the request's lifecycle, Laravel will automatically log it every time we write a log using
Log
facadeContext is only available for each request, and queue job (Octane is covered too). There won't be any conflict or race condition.
Context::add('hello', 'world');
Log::info('Hello'); // you will see the [datetime] Hello { "contexts": { "hello": "world" } } in the log record
Implementation
Create a middleware to generate/accept ID
We'll have 2 scenarios:
If there is a
X-REQUEST-ID
header with a valid ID, we'll continue to use that IDThis will help trace ALL REQUESTS of a particular user's lifecycle.
I'll use
X-REQUEST-ID
, you can use any key that you like ๐ฅน.
If not, we'll generate a new unique ID
Let's hit the command to create a new middleware:
php artisan make:middleware UseRequestId
And the implementation:
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Str;
class UseRequestId
{
public function handle(Request $request, Closure $next): Response
{
// get existing id or generate new one
$requestId = $request->header('X-REQUEST-ID')
?? (string) Str::ulid();
// add to context
Context::add('requestId', $requestId);
return $next($request);
}
}
Register the middleware
Since my application is an API service, I'll apply the middleware to the api
group.
Open bootstrap/app.php
and register it:
use App\Http\Middleware\UseRequestId;
->withMiddleware(function (Middleware $middleware) {
$middleware->prependToGroup(UseRequestId::class);
})
Using prependToGroup
to ensure this middleware will be resolved at the top (global => this middleware => other middleware of api
group)
Add a route and try it out ๐ฅฐ
Add a sample API
// api.php
Route::get('/ping', function () {
Log::info('pong');
return response()->json([]);
});
Open the log file to check (laravel.log
for the basic driver or laravel-{date}.log
for daily
driver).
We should see something like:
[2024-04-28 13:22:59] local.INFO: Pong {"requestId":"01HWJDZ12P3CE4JDY3912YSDEA"}
To ensure it will print the same request ID, you can try to log multiple times/multiple files in the same lifecycle. For example:
Log::info('ping');
// do something
Log::info('pong');
// do something more
Log::info('end');
Result:
[2024-04-28 13:26:35] local.INFO: ping {"requestId":"01HWJE5MD8DQ24DBDN5R8TXK7K"}
[2024-04-28 13:26:35] local.INFO: pong {"requestId":"01HWJE5MD8DQ24DBDN5R8TXK7K"}
[2024-04-28 13:26:35] local.INFO: end {"requestId":"01HWJE5MD8DQ24DBDN5R8TXK7K"}
Context already covered & battle-tested from Laravel too ๐
Add the ID to the response
Last part, we need to send the ID to the API consumers, so they can send the same ID or for error reporting purposes.
We'll transform a bit of the middleware handle
method:
/** @var \Illuminate\Http\Response $response */
$response = $next($request);
return $response->header('X-REQUEST-ID', $requestId);
When hitting the $next
, Laravel will process everything and give us the FINAL RESPONSE (after Error/Exception too).
So we can add the ID into the header (or body if you want) ๐
Result
Now our requests are bound with request IDs. With means:
Easier for us to search for logs of a given ID ๐ Debugging is much faster & better now.
- If you use any logging platform, surely it can search wayyy faster than normal text search โค๏ธ.
Customers/API consumers can know their "request ID", if it has any issue, they can extract the request ID and report the problem to us ๐
Additionally, you can record more data in the Context, e.g.:
Logged In User ID
URL
Timezone
etc
Conclusion
Well, happy coding & happy debugging guys.
Until the next blog post ๐. Thanks for reading!