Quickstart
From install to a live, request-scoped log line in five minutes: you’ll build a manager, register it once, set a request id, and watch it appear in every line automatically.
What you’ll build
A tiny script that logs from a service class and carries a request_id on every line —
the same loop you’ll use in a real app: configure → register → set context → log.
Before you start
Install the package with Monolog so you get real output:
composer require flytachi/winter-logger monolog/monologStep 1 — Build a LoggerManager
The manager takes a ready-made config. In a real app the framework resolves the output
target and context storage for you; here you pass them directly. Use ProcessContext for
FPM/CLI and write to stdout so you can see it in the terminal.
<?php
require 'vendor/autoload.php';
use Monolog\Level;
use Flytachi\Winter\Logger\LoggerManager;
use Flytachi\Winter\Logger\Context\ProcessContext;
$manager = new LoggerManager(
contextStorage: new ProcessContext(),
channels: [
'http' => [
'level' => Level::Debug,
'format' => 'line', // 'line' | 'json'
'output' => 'stdout', // 'stdout' | 'stderr' | 'syslog' | 'file' | 'null'
'file_path' => null,
'file_max' => 30,
'syslog_ident' => 'winter',
],
],
);Every config key is documented in the Channel config reference.
Step 2 — Register it with LoggerFactory
Do this once at bootstrap. After it, any code can log without touching the manager. Set the
default channel so getLogger() and Log::*() know where to write.
use Flytachi\Winter\Logger\LoggerFactory;
LoggerFactory::setManager($manager);
LoggerFactory::setDefaultChannel('http');Step 3 — Set request context once
Set request_id at the start of the request. The ContextInjectingProcessor — added to
every channel automatically — merges it into every subsequent line.
LoggerFactory::contextStorage()->set('request_id', 'req-abc-123');Step 4 — Log from anywhere
Use a per-class logger. The short class name shows up as (ClassName), and the full FQCN
is stored in context for aggregator queries.
// Per-class logger — Java-style:
LoggerFactory::getLogger('App\\Service\\UserService')->info('user created', ['id' => 42]);
// One-line shortcut to the default channel:
use Flytachi\Winter\Logger\Log;
Log::warning('rate limit hit', ['ip' => '10.0.0.1']);Run it:
php log.phpYou’ll see the request_id on both lines, even though you set it only once:
[2024-01-01 12:00:00] [INFO ] -http- [4821] (UserService): user created {"id":42,"class":"App\\Service\\UserService","request_id":"req-abc-123"}
[2024-01-01 12:00:00] [WARN ] -http- [4821]: rate limit hit {"ip":"10.0.0.1","request_id":"req-abc-123"}Step 5 — Bind context to a logger
To attach fields to one logger only (not the whole request), use withContext(). It
returns a new logger; the original is untouched.
$log = LoggerFactory::getLogger('App\\Service\\PaymentService')
->withContext(['order_id' => 5001]);
$log->info('payment started'); // carries order_id
$log->info('payment done'); // carries order_idThe whole thing
<?php
require 'vendor/autoload.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(),
channels: [
'http' => [
'level' => Level::Debug,
'format' => 'line',
'output' => 'stdout',
'file_path' => null,
'file_max' => 30,
'syslog_ident' => 'winter',
],
],
);
LoggerFactory::setManager($manager);
LoggerFactory::setDefaultChannel('http');
LoggerFactory::contextStorage()->set('request_id', 'req-abc-123');
LoggerFactory::getLogger('App\\Service\\UserService')->info('user created', ['id' => 42]);What just happened
- The manager built one Monolog channel (
http) lazily and cached it. ContextInjectingProcessor(automatic) mergedrequest_idinto every line’s context.- The per-class logger rendered
(UserService)and stored the FQCN underclass. SpringLineFormatterproduced the readable line — datetime, level, channel, PID.
Next steps
- Mental model — the three ideas behind everything
- Request context — set & clear per-request fields
- Swoole coroutines — isolate context per request
- Dynamic channels — add channels at runtime
- API reference — every class and method