graham-campbell/logger-core
Core logging utilities for Laravel applications, providing a PSR-3-compatible logger foundation, context handling, and reusable components for Graham Campbell's logger packages. Lightweight and easy to integrate into existing Laravel projects.
Install and confirm what you actually got.
composer require graham-campbell/logger-core
That is the entire setup. There is no service provider to register, no facade, no config/logger-core.php, no vendor:publish step, and no .env keys. The repository is GrahamDeprecated/Laravel-Logger-Core, scores 0.3, and has zero dependents, so treat it as "a small trait I could copy" rather than a library to standardise on. That framing answers most of the questions below.
Read the source before the README. Open vendor/graham-campbell/logger-core/src/ and read the trait in full. It is a handful of lines: a nullable Psr\Log\LoggerInterface property, a lazy getLogger() that constructs a default logger when none was injected, and a setLogger() setter. Two minutes of reading tells you more than the package description, which is partly aspirational (see Gotchas).
The first use case that justifies the dependency. You are writing a class — an SDK-style client, a domain service, a queued job, a collaborator — that needs to log but must not hard-depend on Laravel's Log facade or the logger() helper. You want PSR-3 only, so the same class runs in a Laravel app, a Lumen service, and a plain PHP script. Compose the trait and let the host wire in a real logger:
use GrahamCampbell\LoggerCore\LoggerTrait;
final class PaymentReconciler
{
use LoggerTrait;
public function reconcile(): void
{
$this->getLogger()->info('reconcile started', ['count' => 12]);
}
}
Where it does not fit. Inside a normal Laravel app, Log::, logger(), or constructor-injecting Psr\Log\LoggerInterface already solves this — with channels, handlers, and context configured. If you are reaching for this trait in a controller or an Eloquent model, stop; you already have a logger.
Wire the real logger once, at the container boundary. The trait's lazily-built default logger is not Laravel's logger: no channels, no configured handlers, no production JSON formatter. Resolve the PSR-3 logger from the container and push it in via a binding or small factory, never at call sites:
// AppServiceProvider::register()
$this->app->bind(PaymentReconciler::class, function ($app) {
$reconciler = new PaymentReconciler();
$reconciler->setLogger($app->make(\Psr\Log\LoggerInterface::class));
return $reconciler;
});
In Lumen this is the same pattern, resolved manually in your own service provider registered from bootstrap/app.php — Lumen does no package auto-discovery. Note that the trait itself needs no registration anywhere; only the companion logger packages in the ecosystem do.
Trait as a test seam. The highest-value day-to-day pattern is not production logging — it is injecting a NullLogger or TestLogger so assertions run against records rather than Log::spy():
protected function setUp(): void
{
parent::setUp();
$this->reconciler = new PaymentReconciler();
$this->reconciler->setLogger(new NullLogger());
}
This keeps unit tests framework-free and stops the trait's default logger from writing noise to stderr during the suite.
Package-authoring pattern. If you publish your own package, depend on psr/log plus this trait so your classes log without assuming Laravel or Monolog. The host application injects its logger; your package stays framework-agnostic. This is the use case the package was written for and the only one where it earns its keep.
Prefer constructor injection where you own the constructor.
public function __construct(private LoggerInterface $logger) {}
You get the same decoupling, an explicit dependency, static-analysis visibility, and no shared mutable state. Reach for the trait only where injection is impractical — objects created by the framework, deserialised jobs, legacy call sites.
Queued jobs need serialisation handling. Because the logger lives on the instance, it travels with the object. Null it in __sleep() and re-resolve on wake, or implement __serialize()/__wakeup() to drop it. This is the failure mode most likely to reach production, because it only surfaces when a trait-bearing object is dispatched.
Do not compose it with Psr\Log\LoggerAwareTrait. Both define the same logger property and setLogger/getLogger surface. Check exact names in the source before combining — a duplicate property or signature is a fatal error that appears only when the class autoloads.
The silent-default trap. Forget setLogger() and everything still works — that is the problem. Messages go to an unconfigured logger (often stderr or a no-op), never reach your channels, and never pick up request context or the JSON formatter. Nothing throws. Add a rollout sanity check:
dump(get_class($this->reconciler->getLogger()));
If it is not the Monolog instance Laravel ships, your wiring is missing.
The docs overstate the package. Claims about "context processing", "message interpolation utilities", "extensible architecture for custom handlers and processors", and "official Lumen support" describe an ecosystem the package sits underneath, not what this repository contains. You get a trait plus lazy default construction. Handlers, processors, formatters, and channel routing are Monolog and Laravel's logging config. Do not scope work against the description text.
Check the psr/log major. Laravel has shipped against different psr/log majors; LoggerInterface signatures and the LogLevel class changed across v1/v2/v3. Compare the package's composer.json constraint against what your Laravel version resolves. A too-loose constraint produces signature mismatches that only bite at runtime under strict types.
Per-object logger proliferation. The default is lazy and per-instance. Fine for long-lived services; for code constructing thousands of short-lived objects you get thousands of loggers. Share one via the container instead of relying on the trait's default.
No extension points beyond setLogger(). No driver, config array, middleware, or events. If the behaviour does not fit, copy the ~15 lines into your own namespace, adjust as needed, and control the psr/log constraint yourself. Given the deprecation naming and zero dependents, that is a legitimate first option rather than a fallback — and it protects you from a dependency on the observability path pinning a future framework upgrade.
How can I help you explore Laravel packages today?