czim/laravel-json-context-logging
A Laravel logging package that adds structured JSON context to log entries, helping you attach request, user, and custom data consistently for easier debugging, filtering, and search across application logs.
This package swaps Laravel's default log line formatter for a JSON-aware logger that carries structured context. Before writing any config, check the three things that will actually determine whether it works in your app:
composer.json — confirm the package pulls in cleanly against your Laravel and PHP versions. Last release is 0.9.1 (2019), so if you're on Laravel 9/10/11 or PHP 8.1+, verify compatibility before planning around it. On a modern stack this may be a "vendor and patch" job rather than a composer require.config/logging.php — the package hooks into Laravel's channel system. Read how it exposes its driver/formatter, because you'll be pointing an existing channel (or a new daily/stack channel) at it rather than replacing Laravel's logging wholesale.src/ service provider — this is where the logger factory is bound. The 0.9.1 release fixed the factory so it actually implements its declared interface, so if you're resolving anything through the container, this is the source of truth for what to type-hint.First use case: add structured context to a single non-critical service. Configure one channel with the JSON formatter, dump a request ID into the log context, and confirm the output is valid JSON with your fields present. Don't migrate every channel on day one — start with one destination you can inspect, then expand.
Minimal shape of what you're aiming for:
// A channel in config/logging.php
'json' => [
'driver' => '...', // whatever the package registers
'path' => storage_path('logs/app.json.log'),
// processors/context fields per the package README
],
Then log normally — Log::info('order created', ['order_id' => $id]) — and verify the context lands as structured JSON fields, not interpolated strings.
Correlation IDs via middleware. The highest-value day-one pattern. Generate a request ID in middleware, push it into the log context so every line within a request carries it:
public function handle($request, Closure $next)
{
$id = $request->header('X-Request-Id') ?? (string) Str::uuid();
Log::withContext(['request_id' => $id]); // or the package's context API
return $next($request);
}
If the package predates Log::withContext() (Laravel 8.1+), use its processor mechanism to inject context instead — same outcome, different entry point.
Context processors for ambient fields. Anything that's the same on every line — environment, service name, app version, tenant ID — belongs in a processor, not in every Log::info() call. Wire these up once in the channel config and every log line inherits them. This is the package's core value proposition and the thing to lean on hardest.
Stack channels for multi-destination routing. Keep a human-readable channel (single/daily) alongside the JSON channel in a stack. Developers read the plain channel locally; machines ingest the JSON channel in staging/production. This lets you adopt the package without forcing everyone to parse JSON during local debugging.
User and tenant context. Resolve the authenticated user in a processor rather than manually threading user_id through call sites. Combine with tenant resolution if you're multi-tenant. Centralizing this is the whole point of the "contextual logging" pitch.
Testing integration. Assert the factory resolves from the container and satisfies its interface — the exact regression class 0.9.1 fixed. A cheap smoke test that app(YourFactoryInterface::class) returns an instance will catch a repeat of that bug in CI instead of production. Also log-assert in feature tests (Log::shouldReceive/Log::spy) to confirm context fields are attached where you expect.
Environment-gated verbosity. Use the JSON channel in staging/production where machine parsing matters; keep the default channel for local dev. Don't force JSON on developers who are tailing logs interactively.
It's pre-1.0 and last released in 2019. This is the dominant risk. There is no semver stability guarantee, and there's no recent maintenance to ride. Pin the version, read the changelog at every bump, and treat any signature change as potentially breaking. Budget for the possibility that you'll fork or vendor it — the docs describe it as a thin layer over Monolog/PSR-3, so the exit path is short, which is what makes adopting it defensible at all.
Remove interface workarounds now. If anyone wrote a container binding or wrapper because the factory didn't satisfy its interface before 0.9.1, delete it. A manual binding will mask future interface changes and hide exactly the problems the release fixed. Grep your service providers for bindings that point at the package's factory.
Validate JSON output, don't eyeball it. A common failure is a processor injecting non-serializable context (a model, a closure, a resource) that silently degrades or throws inside the formatter at log time. Add a test that logs a representative payload and json_decodes the resulting file or stream. Do this in CI — logging failures are otherwise silent until you need logs to debug an incident.
Interface conformance exposes custom implementations. If you write your own factory or decorate the package's, its method signatures must now match the enforced interface. An LSP violation that used to slip through will surface as a TypeError on resolution. Audit custom logging classes before upgrading.
Confirm which interface is which. The 0.9.1 note says "the corresponding interface" without naming it. Establish whether it's package-specific, Laravel-specific, or PSR-3 aligned — it determines what you should type-hint and how easily you can swap in a test double. Type-hint the interface, not the concrete class, in your own code.
Log context survives across the request — watch for leakage. Context set once persists for the rest of the request lifecycle and, in queue workers or long-running processes (Octane, Horizon), across jobs if you don't reset it. Clear per-job context in a finally block or job middleware, or you'll get logs attributing one job's work to another.
Check the driver/factory binding if logs go silent. The failure mode here is not a crash — it's logging quietly degrading or a channel resolving to a misbound factory. If log files stop filling after an upgrade, check container resolution of the factory first, before assuming a permissions or path problem.
Don't force JSON on the whole stack at once. Stack channels with a plain-text sibling keep local developer ergonomics intact and give you a fallback if a JSON processor misbehaves. Roll out per-channel and per-environment.
Verify PHP/Laravel support matrix explicitly. Given the package's age, the documented matrix may not match your runtime. If it doesn't install cleanly, evaluate whether a small compatibility PR or a local patch is worth it versus hand-rolling a Monolog JSON formatter — for structured context specifically, the latter is often a 30-line formatter plus a processor.
How can I help you explore Laravel packages today?