cakephp/log
A flexible logging library for CakePHP applications, offering configurable engines, log scopes, and PSR-3-compatible interfaces. It helps route messages to files, syslog, or custom handlers with minimal setup.
cakephp/log is the logging subsystem extracted from CakePHP: a static Log facade, a BaseLog engine base class, and a handful of engines (FileLog, ConsoleLog, SyslogLog, ArrayLog). It is a PSR-3 logger, but it is not a framework — there is no service provider, no config file, no container.
composer require cakephp/log
Watch the version you pull. cakephp/log ^4 requires psr/log ^1|^2; ^5 requires psr/log ^3. Laravel 10/11 ship Monolog 3, which needs psr/log ^3, so prefer cakephp/log ^5 on modern Laravel. It drags in cakephp/core (small) but not the full framework.
Where to look first:
src/Log.php — the whole public API (setConfig, getConfig, write, engine, drop, reset, and the debug()/error()/… shortcuts).src/Engine/BaseLog.php — what your custom engines extend (_format(), getConfig()).src/Engine/FileLog.php — the canonical example of level filtering, file/path config, and scope handling.Minimal standalone setup (note: LOGS is a CakePHP constant that does not exist in Laravel):
use Cake\Log\Log;
use Cake\Log\Engine\FileLog;
Log::setConfig('default', [
'engine' => FileLog::class,
'path' => storage_path('logs'),
'file' => 'cakephp.log',
'levels' => ['error', 'warning', 'info'],
]);
Log::write('info', 'hello from CakePHP logging');
First real use case on a Laravel project: you've installed a CakePHP-adjacent library (or a legacy module) that calls Log::write() / uses LogTrait, and you want those calls to land in your existing Laravel channel instead of a second, orphaned log file. See the forwarding engine in the next section.
Bootstrap once, in a provider. The Log class is a global static registry. Configure it in register() so it is set before any library code runs, and never inside a request path.
// app/Providers/CakeLogServiceProvider.php
public function register(): void
{
Log::setConfig('default', [
'engine' => LaravelLogEngine::class,
'channel' => 'stack',
]);
}
Forward to Laravel instead of duplicating sinks. The highest-value pattern is a custom engine that extends BaseLog and proxies to Illuminate\Support\Facades\Log. This keeps one log destination, one set of handlers, one log-shipping pipeline.
use Cake\Log\Engine\BaseLog;
class LaravelLogEngine extends BaseLog
{
public function log($level, \Stringable|string $message, array $context = []): void
{
\Illuminate\Support\Facades\Log::channel($this->getConfig('channel', 'stack'))
->log($level, (string)$message, $context);
}
}
Channel-per-engine. Log::setConfig('name', [...]) is really "named engine", not "channel". Use several — payments, audit, slow — each with its own levels and destination, then target them per call:
Log::setConfig('audit', [
'engine' => FileLog::class,
'path' => storage_path('logs'),
'file' => 'audit.log',
'levels' => ['notice', 'info', 'warning', 'error'],
]);
Log::write('notice', 'order 42 refunded', ['scope' => ['audit'], 'actor' => 7]);
scope is the routing key. A scope in the context array is how you fan a single engine config across multiple files: 'file' => '{scope}.log' causes scope: ['orders'] to write orders.log. Scope also lets one setConfig('default') serve several logical loggers without extra engine instances.
Per-engine level filtering. levels accepts an array or comma string. Filtering happens inside the engine, so filtered-out writes still cost the call. For high-volume debug traffic, add a cheap guard (Log::engine('debug') returns false when unconfigured) rather than relying on the engine to swallow it.
LogTrait for library classes. Any class using Cake\Log\LogTrait gains a log() method that forwards to Log::write(). This is why the package exists standalone — third-party CakePHP code expects it. You don't need to adopt it in your own classes; you just need it to work when a dependency uses it.
Test with ArrayLog. Swap the engine in a test bootstrap and assert on captured entries; reset afterwards.
Log::setConfig('default', ['engine' => ArrayLog::class]);
// ... exercise code ...
$this->assertStringContainsString('refunded', Log::engine('default')->read()[0]);
Bridging the other direction. If you're porting CakePHP code into Laravel, keep Log::write() calls in the library layer and configure a forwarding engine at the boundary. Don't sprinkle Log:: calls through your Laravel app code — use Laravel's Log there and treat cakephp/log as an adapter layer only.
Log::$_engines and config in Log::$_config. Always Log::reset() in tearDown() (or Log::drop($name) for one), otherwise a config from an earlier test silently wins.setConfig() after an engine has already been materialized for that name will not rebuild it. Use Log::drop($name) to force re-creation. In Octane/long-running workers this matters: config changed in a request is invisible to the next.LOGS, APP, DS constants are CakePHP-only. Any tutorial snippet referencing LOGS will fatal in a Laravel app. Substitute storage_path('logs').'engine' => 'MyPlugin.MyEngine' relies on App::className() from cakephp/core's class loader, which is not wired up outside CakePHP. Always pass Fully\Qualified\ClassName::class.FileLog does not rotate. It appends forever. Point it at a storage/logs/*.log path covered by your existing logrotate config, or use the forwarding engine and let Laravel/Monolog handle rotation.FileLog does not interpolate PSR-3 placeholders. Monolog expands {user} from context; this package's engines do not. Build the message yourself or normalize in your forwarding engine.Log::write('warn', ...) (instead of 'warning') raises InvalidArgumentException; an unconfigured engine name raises RuntimeException — the debug()/error() shorthands are safer than string levels in dynamic code.FileLog fails loudly on a missing directory. path must exist and be writable by the PHP user; there's a mask option if your umask produces group-unreadable files.write($level, $message, array $context); 4.x/5.x use PSR-3's log($level, $message, array $context). If you copy an engine from an old blog post, it will be silently skipped or fatal depending on the version.cakephp/cakephp. Issues/PRs opened here get closed; file them against the monorepo, and pin a released tag rather than a branch alias. Low star count reflects the split, not the maturity — but it also means no community support channel of its own.Cake\Log\Log exists behave correctly inside a Laravel codebase. Adopt it as a compatibility shim, not a logging strategy.How can I help you explore Laravel packages today?