Skip to content

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.

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.

  • logDebug(string $message, array $context = []) method Emits a DEBUG level log record.

  • logInfo(string $message, array $context = []) method Emits an INFO level log record.

  • logNotice(string $message, array $context = []) method Emits a NOTICE level log record.

  • logWarning(string $message, array $context = []) method Emits a WARNING level log record.

  • logError(string $message, array $context = []) method Emits an ERROR level log record.

  • logCritical(string $message, array $context = []) method Emits a CRITICAL level log record.

  • logAlert(string $message, array $context = []) method Emits an ALERT level log record.

  • logEmergency(string $message, array $context = []) method Emits an EMERGENCY level log record.

  • logException(Throwable $ex, string $message = '', array $context = []) method Emits an ERROR record, prepends $message, and appends the full backtrace.

  • logEx(Throwable $e, string $message = '', array $context = []) method Emits an ERROR record with the exception class, code, message, file, and line.

OrderService.php
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.

Set winter.web.request.enableTrace: true in application.yml (default false) to log two INFO lines per HTTP exchange from the dispatcher:

config/application.yml
winter:
web:
request:
enableTrace: true
TRACE START POST /api/orders
TRACE FINISH POST /api/orders -> 200 (41.0 ms)
  • The START line is emitted the instant a request reaches the dispatcher (method + URI only — no status or duration exists yet).
  • The FINISH line is emitted on completion, whether the request succeeded or failed (method + URI + response status + time taken).
  • A START line with no matching FINISH means 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.

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.

config/logger.yml
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\MemoryUsageProcessor
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.

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).

config/logger.yml
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 only

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.