Configure Structured Logging in Your Winter Boot App
Winter Boot logs through Monolog. Add the Wlf4p trait to a class and it gets a ready-made logging API: the trait picks the right logger and log level for the class — including per-class or per-namespace overrides from logger.yml — so you never manage logger instances yourself.
The Wlf4p Trait
Section titled “The Wlf4p Trait”Add use Wlf4p; to any class to get the logging methods. They are static (call them as self::logInfo(...)), and each record is logged under the name of the class that uses the trait.
Available Methods
Section titled “Available Methods”-
logDebug(string $message, array $context = [])methodEmits aDEBUGlevel log record. -
logInfo(string $message, array $context = [])methodEmits anINFOlevel log record. -
logNotice(string $message, array $context = [])methodEmits aNOTICElevel log record. -
logWarning(string $message, array $context = [])methodEmits aWARNINGlevel log record. -
logError(string $message, array $context = [])methodEmits anERRORlevel log record. -
logCritical(string $message, array $context = [])methodEmits aCRITICALlevel log record. -
logAlert(string $message, array $context = [])methodEmits anALERTlevel log record. -
logEmergency(string $message, array $context = [])methodEmits anEMERGENCYlevel log record. -
logException(Throwable $ex, string $message = '', array $context = [])methodEmits anERRORrecord, prepends$message, and appends the full backtrace. -
logEx(Throwable $e, string $message = '', array $context = [])methodEmits anERRORrecord with the exception class, code, message, file, and line.
Usage Example
Section titled “Usage Example”use dev\winterframework\util\log\Wlf4p;
class OrderService{ use Wlf4p;
public function processOrder(int $orderId): void { self::logInfo('Processing order', ['orderId' => $orderId]);
try { // … business logic … self::logDebug('Order processed successfully', ['orderId' => $orderId]); } catch (\Throwable $e) { self::logException($e, 'Failed to process order: '); } }}What the Framework Logs for Request Errors
Section titled “What the Framework Logs for Request Errors”Winter Boot separates errors your server should fix from errors the client caused (since 2.1.6):
| Situation | Level | Stack trace |
|---|---|---|
| a controller throws an exception that becomes a 5xx | ERROR |
yes |
a controller throws HttpRestException with a 4xx status |
INFO: status and route only (the exception message is app text and may contain request data, so it isn’t logged) |
no |
a query parameter or request body can’t be converted, e.g. limit=abc for an int (answered 400) |
DEBUG, e.g. Parameter "limit": Cannot assign a value of type string to type "int" |
no |
| no route matches the URL (404) | INFO |
no |
#[Transactional] begins or commits a transaction |
DEBUG |
no |
a #[Transactional] method throws |
INFO: one line saying whether the transaction rolled back. The exception itself is reported by whoever handles it |
no |
To see the DEBUG lines for one area, raise its level in logger.yml, for example dev\winterframework\core\web: DEBUG under custom_level.
Request Trace Logging
Section titled “Request Trace Logging”Set winter.web.request.enableTrace: true in application.yml (default false) to log two INFO lines per HTTP exchange from the dispatcher:
winter: web: request: enableTrace: trueTRACE START POST /api/ordersTRACE FINISH POST /api/orders -> 200 (41.0 ms)- The
STARTline is emitted the instant a request reaches the dispatcher (method + URI only — no status or duration exists yet). - The
FINISHline is emitted on completion, whether the request succeeded or failed (method + URI + response status + time taken). - A
STARTline with no matchingFINISHmeans the worker died mid-request — itself a useful signal. - Only safe metadata is logged: the URI is sanitized (control characters stripped, length capped). Headers, bodies, and query values are never logged.
Configuration — logger.yml
Section titled “Configuration — logger.yml”Without any configuration, Winter Boot logs to the console (stdout). To change that, create logger.yml in your config directory (or logger-{profile}.yml when a profile is set). The file follows the monolog-cascade schema, which declares loggers, handlers, formatters, and processors in YAML.
loggers: myLogger: handlers: [ info_file_handler ] processors: [ web_processor ] custom_level: # Override log level per fully-qualified class name or namespace prefix. # Only log messages at or above the given level will be emitted. some\namespace\SomeClass: DEBUG another\namespace\AnotherClass: ERROR other\namespace: INFO other\namespace\SilentClass: NONE # suppress all logging for this class
formatters: dashed: class: Monolog\Formatter\LineFormatter format: "%datetime%-%channel%.%level_name% - %message%\n"
handlers: console: class: Monolog\Handler\StreamHandler level: DEBUG formatter: dashed processors: [ memory_processor ] stream: php://stdout
info_file_handler: class: Monolog\Handler\StreamHandler level: INFO formatter: dashed stream: ../logs/MyApp.log
processors: web_processor: class: Monolog\Processor\WebProcessor
memory_processor: class: Monolog\Processor\MemoryUsageProcessorKey Configuration Sections
Section titled “Key Configuration Sections”loggers
Defines one or more named loggers. Each logger references the handlers and processors it uses. The custom_level map lets you control the effective log level for individual classes or entire namespaces without touching your code.
formatters
Declares Monolog formatter instances. LineFormatter supports a custom format string using Monolog’s placeholder tokens: %datetime%, %channel%, %level_name%, and %message%.
handlers
Declares where log records are written. StreamHandler writes to files or streams such as php://stdout. Each handler references a formatter and optionally its own processors.
processors
Declares Monolog processors that enrich log records. WebProcessor appends HTTP request details; MemoryUsageProcessor appends current memory usage.
Per-Class Log Levels with custom_level
Section titled “Per-Class Log Levels with custom_level”The custom_level map under a logger accepts fully-qualified class names or namespace prefixes as keys. The longest matching prefix wins. This lets you enable verbose debugging for a single service while keeping everything else at a higher threshold.
Supported levels: DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY, and NONE (suppresses all output).
loggers: myLogger: handlers: [ info_file_handler ] custom_level: app\service\PaymentService: DEBUG # verbose for payment debugging app\service: WARNING # all other services: warnings and above app: ERROR # everything else in app\: errors onlyMonolog 3
Section titled “Monolog 3”Winter Boot 2.2.0 and later use Monolog 3. Existing logger.yml files work unchanged, including level names such as level: DEBUG. If you wrote your own processor, formatter or handler class, it now receives a Monolog\LogRecord object instead of an array: read $record->context and $record->extra instead of $record['context'], and use the Monolog\Level enum (Level::Warning) instead of the deprecated Logger::WARNING constants. See Monolog’s upgrade notes for the full list.