Using Context in Middleware
Middleware is one of the best places to set Ledgerly context automatically.
This allows metadata such as:
- tenant_id
- request_id
- region
- service
- feature flags
to be attached to every entry created during a request.
This avoids repeating metadata in controllers, services, or jobs.
Basic Example
Example middleware that sets the tenant context:
namespace App\Http\Middleware;
use Closure;
class AttachTenantContext
{
public function handle($request, Closure $next)
{
if ($tenant = tenant()) {
ledgerly()->context([
'tenant_id' => $tenant->id,
]);
}
return $next($request);
}
}
All ledger entries logged during this request will now include:
tenant_id
Registering Middleware
Register the middleware in:
app/Http/Kernel.php
Example:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\AttachTenantContext::class,
],
];
Adding Multiple Context Values
You can attach multiple values:
ledgerly()->context([
'tenant_id' => $tenant->id,
'region' => $tenant->region,
'plan' => $tenant->plan,
]);
This metadata will automatically be included in all entries for the request.
Context in API Middleware
For APIs, you may extract values from headers:
ledgerly()->context([
'request_id' => $request->header('X-Request-ID'),
'correlation_id' => $request->header('X-Correlation-ID'),
]);
This allows tracing across services.
Context in Multi-Tenant Applications
A common SaaS pattern:
Tenant resolution middleware:
$tenant = TenantResolver::resolve($request);
ledgerly()->context([
'tenant_id' => $tenant->id,
]);
Now all audit logs automatically include tenant information.
Context in Queue Jobs
If jobs need context, pass values explicitly:
dispatch(new ProcessImportJob($tenantId));
Inside the job:
public function handle()
{
ledgerly()->context([
'tenant_id' => $this->tenantId,
]);
ledgerly()->action('import.started')->log();
}
Context is scoped to the job execution.
Using Scoped Context in Middleware
Scoped context ensures isolation:
return ledgerly()->withContext([
'tenant_id' => $tenant->id,
], function () use ($request, $next) {
return $next($request);
});
This ensures context is automatically restored after the request finishes.
This approach is recommended for long-lived processes.
Middleware Order Considerations
Context middleware should run:
- After authentication (if an actor depends on auth)
- After tenant resolution
- Before controllers execute
Typical order:
Auth
Tenant Resolver
Ledgerly Context Middleware
Controllers
Best Practices
Recommended:
- Attach tenant_id in middleware
- Attach request_id if available
- Keep the context small
- Use scoped context when possible
Avoid:
- Storing large objects in context
- Storing sensitive values
- Performing heavy logic in middleware
Debugging Context
To verify context is working:
- Log an entry in a controller
- Inspect metadata in the database
- Confirm context values are present
Example: Full Request Flow
- Middleware sets context
- Controller logs entries
- Entries automatically include metadata
Controller:
ledgerly()
->action('invoice.created')
->log();
Metadata:
tenant_id = 42
request_id = abc123
source = http
When NOT to Use Middleware
Do not use middleware when:
- Context applies only to one operation
- Values are computed deep in services
- Context applies only inside a transaction
In these cases, use scoped context instead.