The first time this matters is usually a support ticket. A customer says checkout failed at 2pm, and you need to find what actually happened to them — not what happened to everyone, just them. If your telemetry doesn't know who hit each request, the answer is grep and guesswork. Attaching user identity to every event turns that question into a filter.
Middleware approach
A middleware runs on every authenticated request. Push user context into a request-scoped container your telemetry tool reads.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class AttachUserTelemetry
{
public function handle($request, Closure $next)
{
if (Auth::check()) {
$user = Auth::user();
// Push into Laravel's log context — appears on every log line
Log::withContext([
'user_id' => $user->id,
'user_email' => $user->email,
'user_plan' => $user->subscription_plan ?? null,
]);
// For monitoring SDKs that expose a user-setter (NightOwl / Nightwatch)
if (function_exists('nightwatch')) {
nightwatch()->setUser([
'id' => $user->id,
'email' => $user->email,
'plan' => $user->subscription_plan ?? null,
]);
}
}
return $next($request);
}
}Register it on routes that should be tracked:
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\AttachUserTelemetry::class,
]);
$middleware->api(append: [
\App\Http\Middleware\AttachUserTelemetry::class,
]);
})What to attach (and what not to)
ATTACH
- User ID (stable, non-secret)
- Email (useful for support)
- Plan or subscription tier
- Team / org ID for B2B apps
- Feature flag variant
AVOID
- Passwords, password hashes
- Payment card info, bank details
- Government IDs, SSNs
- Full addresses, phone numbers
- Any field users haven't consented to share
Frontend → backend correlation
For full visibility, your front end should generate a request ID and pass it via header on every API call. The backend picks it up and tags all telemetry with it.
Frontend (every fetch)
const requestId = crypto.randomUUID();
fetch('/api/orders', {
headers: {
'X-Request-Id': requestId,
// ...
},
});public function handle($request, Closure $next)
{
$id = $request->header('X-Request-Id') ?? (string) \Illuminate\Support\Str::uuid();
$request->attributes->set('request_id', $id);
Log::withContext(['request_id' => $id]);
return tap($next($request), fn ($response) => $response->header('X-Request-Id', $id));
}Two places it quietly breaks
Middleware order. The user isn't authenticated until Laravel's auth middleware has run, so your telemetry middleware has to come after it in the stack. Put it earlier and Auth::check() is false and you attach nothing. If user IDs are missing on some routes but not others, check the middleware order on those routes first.
Queued jobs. User context attached to a request does not survive into a job that request dispatched. The job runs later, in another process, with no authenticated user. If you want the job's telemetry tagged with the user who triggered it, pass the ID into the job's constructor and re-attach it at the top of handle(). The same applies to scheduled commands, broadcast events, and any other async work — the identity is only as good as the boundaries it survives.
THE EASY WAY
NightOwl ships with a per-user view
The Nightwatch package captures user_id and email automatically when attached to the request. NightOwl exposes a Users page; click a user to see their detail view with FILTER BY tabs for Requests, Jobs, Exceptions, and Logs in one place. Perfect for support investigations.
composer require nightowl/agent
php artisan nightowl:install