Framework integration
The library is infrastructure-agnostic: it never reads env vars or detects the runtime. Your bootstrap does that, builds a config, and wires the logger. Here’s the split of responsibilities and the exact call order.
Who does what
| Step | Responsibility | Who calls it |
|---|---|---|
| Resolve env → config (levels, output, paths) | infrastructure | framework kernel |
Build the LoggerManager |
infrastructure | framework kernel |
setManager() |
register once | framework kernel |
setDefaultChannel() |
pick active channel | entry point |
setContextStorage() |
pick isolation strategy | entry point (Swoole) |
The kernel builds and registers the manager. The entry point — public/index.php,
the Swoole runner, or a CLI script — knows the actual runtime, so it sets the default channel
and (for Swoole) the context storage.
Bootstrap — register the manager once
<?php
use Monolog\Level;
use Flytachi\Winter\Logger\LoggerManager;
use Flytachi\Winter\Logger\LoggerFactory;
use Flytachi\Winter\Logger\Context\ProcessContext;
$manager = new LoggerManager(
contextStorage: new ProcessContext(), // default; swapped per-runtime below
channels: [
'sys' => ['level' => Level::Warning, 'format' => 'line', 'output' => 'stderr',
'file_path' => null, 'file_max' => 30, 'syslog_ident' => 'winter'],
'http' => ['level' => Level::Info, 'format' => 'line', 'output' => 'stderr',
'file_path' => null, 'file_max' => 30, 'syslog_ident' => 'winter'],
'cli' => ['level' => Level::Debug, 'format' => 'line', 'output' => 'stdout',
'file_path' => null, 'file_max' => 30, 'syslog_ident' => 'winter'],
],
);
LoggerFactory::setManager($manager);Entry points — pick channel & storage per runtime
Each entry point requires the bootstrap, then declares its runtime:
<?php
require '../bootstrap.php';
// FPM: one request = one process, ProcessContext is already correct.
LoggerFactory::setDefaultChannel('http');<?php
require '../bootstrap.php';
use Flytachi\Winter\Logger\Context\CoroutineContext;
// Swoole: swap to coroutine-isolated context before accepting requests.
LoggerFactory::setContextStorage(new CoroutineContext());
LoggerFactory::setDefaultChannel('http');<?php
require 'bootstrap.php';
// CLI jobs / queue workers / daemons.
LoggerFactory::setDefaultChannel('cli');Order matters
setManager() first, always. setContextStorage() and addChannel() operate on the
current manager, so they must run after it. Each of these also clears the per-class logger
cache, so set them at bootstrap — not mid-request.
Use it from application code
Once wired, application code never sees the manager:
use Flytachi\Winter\Logger\LoggerFactory;
use Flytachi\Winter\Logger\Log;
// Per-class logger on the default channel:
LoggerFactory::getLogger(self::class)->info('user created', ['id' => 42]);
// Quick one-liner:
Log::warning('rate limit hit');Recommended default channels
A common convention (as used in winter-kernel):
| Channel | Typical use | Default output |
|---|---|---|
sys |
system events, fallback | stderr |
http |
controllers, services (FPM/Swoole) | stderr (FPM) / stdout (Swoole) |
cli |
jobs, queue workers, daemons | stdout |
stderr for FPM, stdout for Swoole
Under FPM, php://stdout is the FastCGI response stream — writing to it after a client
disconnect risks a broken pipe. Prefer stderr there. Swoole handles responses via its own
I/O, so stdout is safe. See Output & broken pipe.
Reset in tests
LoggerFactory holds static state. Rebuild it in setUp() so tests don’t share a manager:
protected function setUp(): void
{
LoggerFactory::setManager($this->buildManager());
}Related
- Dynamic channels — register extra channels at runtime
- Swoole coroutines — the Swoole entry point in full
- API reference → LoggerFactory