yiisoft/log-target-file
File log target for Yii's logging library that writes application messages to configurable log files. Supports level and category filtering, custom formatting, size-based rotation, and retention of rotated files—a straightforward choice for persistent file-based logging in Yii apps.
Install the package and its logging core:
composer require yiisoft/log-target-file
This pulls in yiisoft/log (which provides Logger, LoggerInterface, and AbstractTarget). There is nothing to configure out of the box — the package is a target, not a framework glue layer.
Minimal wiring
use Yiisoft\Log\Logger;
use Yiisoft\Log\Target\File\FileTarget;
$target = new FileTarget('/var/www/app/runtime/logs/app.log');
$logger = new Logger(['file' => $target]);
$logger->info('User {id} logged in', ['id' => 42]);
$logger->error('Payment failed', ['orderId' => 1001, 'exception' => $e]);
Where to look first
FileTarget — the class you will actually touch. Public API: setLogFile(), setFileRotator(), setFormat(), and the setLevels() inherited from AbstractTarget.FileRotator — size-based rotation, retention count, compression, file mode.FileRotatorInterface — implement this if you want a custom rotation policy (e.g. daily rotation, size-aware naming, S3 upload after rotation).First real use case
An application error/log file in runtime/logs/app.log with size-based rotation:
$target = new FileTarget('/var/www/app/runtime/logs/app.log');
$target->setFileRotator(new FileRotator(
maxFileSize: 10 * 1024 * 1024, // 10 MB
maxFiles: 5,
compress: true,
));
$logger = new Logger(['file' => $target]);
DI container configuration (Yii3 style)
In a Yii3 application, register the target once and let LoggerInterface be autowired. The typical place is config/common/di/logger.php:
use Yiisoft\Log\Logger;
use Yiisoft\Log\LoggerInterface;
use Yiisoft\Log\Target\File\FileTarget;
use Yiisoft\Log\Target\File\FileRotator;
use Yiisoft\Definitions\Reference;
return [
LoggerInterface::class => [
'class' => Logger::class,
'__construct()' => [
'targets' => [
'file' => Reference::to(FileTarget::class),
],
],
],
FileTarget::class => [
'__construct()' => [
'logFile' => '@runtime/logs/app.log',
],
'setFileRotator()' => [
new FileRotator(maxFileSize: 10_485_760, maxFiles: 5, compress: true),
],
],
];
Multiple targets
Logger accepts a map of targets keyed by name. Use one for structured app logs and another for errors-only:
$logger = new Logger([
'file' => $appFileTarget, // all levels
'errors' => $errorsFileTarget, // errors only
]);
$errorsFileTarget->setLevels(['error', 'warning']);
This is the cheapest way to get a separate errors.log without writing any custom filtering logic.
Placeholder formatting
The target renders messages using the Yii format string, so keep log calls structured:
// In the message
$logger->warning('Slow query {duration}ms: {sql}', [
'duration' => 1200,
'sql' => 'SELECT ...',
]);
// Custom format (default is roughly "{timestamp} [{level}] {message}")
$target->setFormat("{timestamp} {level} {message}\n");
Customize the timestamp via setTimestampFormat() if you need ISO 8601, RFC 5424, etc. Prefer a UTC format for correlation across services.
Rotation in production
FileRotator triggers during a write whenever the file would exceed maxFileSize. Set maxFiles to match your retention budget, and enable compress (gzip) if you're disk-constrained. Rotated files are named with a numeric suffix (app.log.1, app.log.2, …), oldest first, and compressed ones get .gz.
Long-running workers
The target buffers through the logger; in short-lived requests, PHP flushes on shutdown. In queue workers, cron daemons, or Swoole/RoadRunner processes:
$logger->flush(); // after each job or batch
Alternatively, call flush() at natural boundaries (end of a job, before sleeping) rather than every line — flushing per message defeats the buffering benefit.
Writable path is on you. The target requires the parent directory of logFile to exist and be writable. Missing directories or a read-only mount throws a RuntimeException on the first write, not at construction — so a misconfigured path fails silently until the first log line, often inside an error handler where the failure is masked. Validate the path in a bootstrap step.
Permission mode quirks. The file is created with a default mode you can override through FileRotator (fileMode, typically 0775). On multi-user hosts, default umask may produce group-unreadable files; set fileMode explicitly if a log shipper runs as another user.
Default level filter drops debug. AbstractTarget historically filters to a conservative set (error, warning, info, trace, etc.). If your debug() calls don't appear, call setLevels($target->getLevels() + ['debug']) — the whitelist is exact, not severity-ordered. In production, keep debug off.
Rotation is not scheduled. It is evaluated on write. A low-traffic service can sit at 9.9 MB forever without ever rotating. If you need time-based rotation (daily, hourly), FileRotator won't help — implement FileRotatorInterface and check the mtime in rotate().
Compression requires zlib. compress: true uses gzip; if ext-zlib isn't loaded, rotation can fail at exactly the wrong moment (when the file is full). Add ext-zlib to composer.json requirements.
Open file handles and copy mode. On Windows or when another process holds the file, renaming a rotated file can fail. The rotator's copy-then-truncate behavior (rotateByCopy) exists for this; enable it if you see rotation errors on Windows or on network filesystems.
Concurrency. Multiple PHP-FPM workers appending to the same file are generally fine on local filesystems (append writes are atomic for small messages), but a single multi-line record can interleave with another worker's. Keep messages short, and avoid writing from both a web process and a long-running worker to the same file if you care about ordering. For high-volume concurrency, log to a dedicated file per pool or ship via a socket target instead.
Timestamp source. The timestamp comes from time(), not from the message — pass time() explicitly into context if you need the event time rather than the write time (matters for buffered long-running workers).
Testing. FileTarget writes to real files; in tests, point logFile at a tempnam() path under sys_get_temp_dir() and unlink it in tearDown. flush() immediately after the log call before asserting contents — otherwise the buffered message isn't on disk yet.
Extension point. All meaningful customization (rotation naming, retention, compression, external shipping) goes through FileRotatorInterface::rotate(). Implement it and inject with setFileRotator() — no subclassing of FileTarget is needed.
How can I help you explore Laravel packages today?