zetacomponents/event-log
The Zeta Components EventLog provides a simple, database-backed event logging system for PHP applications. Record events with timestamps, sources, and custom data, then query, filter, and audit them through a clean API.
Install the package and its base dependency:
composer require zetacomponents/event-log
The package is not namespaced — everything lives under the ezc prefix (ezcLog, ezcLogMessage, ezcLogFileWriter, …). A minimal working setup is a single bootstrap file that configures the singleton once and then logs through it:
<?php
require __DIR__ . '/vendor/autoload.php';
$log = ezcLog::getInstance();
// A mapper decides which writer(s) receive a message.
$mapper = new ezcLogFilterSet();
$log->setMapper($mapper);
// Catch-all filter -> file writer.
$mapper->append(
new ezcLogFilter(),
new ezcLogFileWriter(__DIR__ . '/var/log', 'app.log')
);
$log->log('Application booted', ezcLog::INFO, 'bootstrap', 'startup');
The first real use case is replacing scattered error_log() / file_put_contents() calls with one configured sink: keep the four lines above in your bootstrap (or a small factory), then call ezcLog::getInstance()->log($message, $severity, $source, $category) from anywhere.
Where to look first in src/:
ezcLog — the facade/singleton you call; also holds the severity constants (DEBUG, INFO, NOTICE, WARNING, ERROR, FATAL).ezcLogFilterSet + ezcLogFilter — the routing layer.ezcLogFileWriter — the writer you will use 90% of the time.ezcLogMessage — the value object handed to writers (message, severity, source, category).ezcLogWriter and ezcLogMapper interfaces — the two extension points.tests/ directory — the most reliable executable documentation for this package.Configure once, inject everywhere. Build the mapper/writer wiring in your bootstrap or DI container definition and expose the configured ezcLog instance (or a thin wrapper) to application code. Re-configuring the mapper inside request handlers is a recipe for duplicated log lines.
Wrap it in PSR-3. The package predates PSR-3 and has no adapter. A ~30-line adapter keeps application code portable and lets you swap in Monolog later without touching call sites:
final class EzcLogger extends \Psr\Log\AbstractLogger
{
private const LEVELS = [
'debug' => \ezcLog::DEBUG,
'info' => \ezcLog::INFO,
'notice' => \ezcLog::NOTICE,
'warning' => \ezcLog::WARNING,
'error' => \ezcLog::ERROR,
'critical' => \ezcLog::FATAL,
'alert' => \ezcLog::FATAL,
'emergency' => \ezcLog::FATAL,
];
public function __construct(
private \ezcLog $log,
private string $source = 'app',
private string $category = 'general',
) {}
public function log($level, $message, array $context = []): void
{
// ezcLog has no placeholder interpolation; do it yourself.
foreach ($context as $key => $value) {
$message = str_replace('{' . $key . '}', (string) $value, (string) $message);
}
$this->log->log(
(string) $message,
self::LEVELS[$level] ?? \ezcLog::INFO,
$this->source,
$this->category
);
}
}
Use source and category as your routing dimensions. Convention that ages well: source = module/component (billing, auth, queue), category = domain or channel (payments, security, import). These are the two fields filters match on, so they are your only lever for splitting logs without parsing message text.
Fan out to multiple writers. Register several filter+writer pairs on the same ezcLogFilterSet; a message can reach more than one sink. Typical layout:
$mapper->append(new ezcLogFilter(), new ezcLogFileWriter($dir, 'app.log'));
$errors = new ezcLogFilter();
// Restrict the filter to the severities you care about (see ezcLogFilter
// for the exact property/method names in your installed version).
// $errors->severity = [ezcLog::ERROR, ezcLog::FATAL];
$mapper->append($errors, new ezcLogFileWriter($dir, 'error.log'));
Custom writer for tests and in-process inspection. This is the cleanest way to assert on logs, since no memory/array writer ships with the package:
final class CollectingWriter implements \ezcLogWriter
{
public array $messages = [];
public function writeLogMessage(\ezcLogMessage $message): void
{
$this->messages[] = $message;
}
}
Register it on a fresh ezcLogFilterSet in setUp(), then assert on severity/source/category/message.
Custom mapper for conditional sinks. Implement ezcLogMapper (getWriter(ezcLogMessage $message)) when routing logic outgrows filter objects — for example "ERROR to file + Slack, everything else to file only", or sampling debug logs in production.
Use the database writer only for audit trails. ezcLogDatabaseWriter requires a live ezcDbHandler and a pre-created table, which drags in zetacomponents/database. It is suitable for compliance-style logging, not for high-throughput application logs.
NOASSERTION. The upstream Zeta Components project was Apache-2.0, but the package metadata does not state it. Confirm with legal/LICENSE in the repo before shipping.ezcLog::getInstance() holds its mapper for the whole process. Always call setMapper(new ezcLogFilterSet()) in setUp()/tearDown(), otherwise assertions pass or fail depending on test order.ezcLogFilter configuration (severity as scalar vs. array, constructor vs. properties) is version-sensitive. Read the installed ezcLogFilter source before copying snippets from old eZ Components documentation — those docs are archived and often describe a different minor version.zetacomponents/base and the legacy ezc autoloading convention. If ezcLog class-not-found appears, check that zetacomponents/base installed and that Composer's classmap actually includes the src/ tree; a manual require of the package bootstrap is a workable stopgap.try/catch and degrade to error_log().ezcLogFileWriter (rotation settings if your version exposes them, otherwise external logrotate). Without it, a noisy source will fill the disk.How can I help you explore Laravel packages today?