Skip to content

Logging

The framework binds Psr\Log\LoggerInterface to a Monolog Logger instance. The active area is used as the logger channel name. Handlers and processors are registered via DI tags so any module can extend the logging pipeline without modifying framework configuration.


Log destinations

The logger is configured at boot based on the AMA_LOG_TO_STREAM environment variable.

Mode Condition Destination
File (default) AMA_LOG_TO_STREAM not set var/log/app_{area}_{mode}.log
Stream AMA_LOG_TO_STREAM is set, HTTP areas only php://stdout (info+) and php://stderr (info+), coloured line format

Stream mode is intended for container environments where logs are collected from stdout/stderr. File mode is the default for traditional server deployments.


Injecting the logger

Inject LoggerInterface directly. The resolved instance is the application logger for the current area.

use Psr\Log\LoggerInterface;

public function __construct(
    private readonly LoggerInterface $logger
) {}

Named loggers

Several subsystems use dedicated loggers so their output can be routed separately. Inject them with the #[Inject] attribute:

use Amarant\Framework\Attribute\Di\Inject;
use Amarant\Framework\Enum\DiEnum;
use Psr\Log\LoggerInterface;

public function __construct(
    #[Inject(DiEnum::LOGGER_MESSAGING)]
    private readonly LoggerInterface $messagingLogger
) {}
DiEnum case Channel Used by
LOGGER_MESSAGING messaging Message bus and workers. Writes to var/log/messaging.log.
LOGGER_MESSAGING_BULK_OPERATIONS messaging_bulk Bulk message operations. Uses BulkLogHandler in addition to console output.
LOGGER_SCHEDULING scheduling Job scheduler.
LOGGER_EMAIL email Email sending.
LOGGER_DB db Database queries (when query logging is enabled).

Adding a custom handler

Tag a handler binding with DiEnum::LOGGER_HANDLER to attach it to the main application logger.

Vendor/ModuleName/Configuration/Di.php
use Amarant\Framework\Contract\Di\ConfigurableContainerInterface;
use Amarant\Framework\Enum\DiEnum;
use Monolog\Handler\StreamHandler;
use Monolog\Level;

$container
    ->bind(
        'logger.handler.vendor.module',
        fn (ConfigurableContainerInterface $container) => $container->getBindBuilder()
            ->type(StreamHandler::class)
            ->argument('stream', '{app.root}/var/log/module.log')
            ->argument('level', Level::Warning)
            ->build()
    )
    ->tag(['logger.handler.vendor.module'], [DiEnum::LOGGER_HANDLER]);

The built-in DiEnum::LOGGER_HANDLER_CONSOLE tag identifies a handler that writes to the console — used in development and injected into named loggers to surface output during CLI commands.


Adding a custom processor

Tag a processor binding with DiEnum::LOGGER_PROCESSOR to inject additional context into every log record.

Vendor/ModuleName/Configuration/Di.php
$container
    ->bindClass(MyLogProcessor::class)
    ->tag([MyLogProcessor::class], [DiEnum::LOGGER_PROCESSOR]);

A processor implements Monolog\Processor\ProcessorInterface (callable — receives a LogRecord, returns a LogRecord).


RequestProcessor

Amarant\Framework\Logger\Processor\RequestProcessor is automatically tagged with DiEnum::LOGGER_PROCESSOR for HTTP areas (frontend and backend). It appends the following fields to the extra block of every log record while a request is being processed:

Field Source
request_id RequestContextInterface::getRequestId()
route_name Matched route name, or N/A
identity_type Current identity type, or N/A
identity_id Current identity ID, or N/A
merchant_public_id MerchantContextInterface::getPublicId()
merchant_private_id MerchantContextInterface::getPrivateId()
area Current area (frontend / backend)

This makes every log line traceable back to the originating request without adding context manually.


BulkLogHandler

Amarant\Framework\Logger\Handler\BulkLogHandler persists log records to the BulkLog database table instead of a file. It is used by messaging workers and any other long-running operation that needs its log output stored for later inspection.

To route a log record to the bulk log, pass bulkId and bulkType in the context:

$this->logger->info('Processing item', [
    'bulkId'   => $operationId,   // scalar identifier for the batch
    'bulkType' => 'product_sync', // string type key for grouping
]);

$this->logger->error('Item failed', [
    'bulkId'     => $operationId,
    'bulkType'   => 'product_sync',
    'bulkStatus' => 'failed',     // optional: updates BulkLogStatus for this bulkId
]);

Records without bulkId or bulkType in their context are silently ignored by this handler. Monolog level maps to BulkLog message type: errorerror, warningwarning, everything else → info.


Journal

The Journal is a persistent audit log stored in the database. It is separate from the PSR logger — entries are structured records rather than plain text lines.

Request journal

Amarant\Framework\EventSubscriber\Request\JournalEventSubscriber fires on EventEnum::APP_REQUEST_RESPONSE_FINALIZED and records every mutating HTTP request (POST, PUT, PATCH, DELETE) as a JournalEntry.

Each entry captures:

  • Route name and full URI
  • Request method and content-type
  • Request body (parsed if JSON), query parameters, form parameters, file uploads
  • Request headers
  • Response body (truncated to 32 KB), status code, response headers
  • Exception data (if the request threw)
  • Identity information (type, ID, access scopes, data scopes, impersonator if present)

Sensitive data is automatically redacted. The following are masked with *****:

  • Headers: authorization, cookie, set-cookie
  • Any parameter or query key whose name contains pass, key, token, code, or secret

Request journalling is disabled by default and enabled per area via configuration:

ConfigEnum case Description
JOURNAL_FRONTEND_REQUEST_ENABLED Enable request journalling for the frontend area.
JOURNAL_BACKEND_REQUEST_ENABLED Enable request journalling for the backend area.

Journal structure

A Journal groups related entries by type. Built-in types are constants on Amarant\Framework\DataModel\Journal\Journal:

Constant Value Purpose
JOURNAL_TYPE_REQUEST request HTTP request audit trail.
JOURNAL_TYPE_SYSTEM system System-level operational events.

Each JournalEntry has a severity level:

Constant Value Set when
JournalEntry::LEVEL_INFO 1 Default.
JournalEntry::LEVEL_WARNING 3 Bulk log had warnings.
JournalEntry::LEVEL_ERROR 2 Response was a 4xx/5xx, or bulk log had errors.

BulkToJournalFacade

Amarant\Framework\Journal\BulkToJournalFacade consolidates bulk log entries (written during a background operation via BulkLogHandler) into a single JournalEntry, then deletes the source bulk log records.

$this->bulkToJournalFacade->execute(
    bulkId:              $operationId,
    journalType:         Journal::JOURNAL_TYPE_SYSTEM,
    journalIdentityType: 'system',
    journalArea:         AreaInterface::AREA_CLI,
    bulkType:            'product_sync',          // optional bulk type filter
    journalIdentityId:   null,                    // optional identity ID
    journalIdentifiers:  ['product_sync', $batchId] // optional searchable identifiers
);

The severity of the resulting JournalEntry is derived from the highest level found in the bulk log records (error > warning > info).

CleanupFacade

Amarant\Framework\Journal\CleanupFacade deletes BulkLog, BulkLogStatus, and JournalEntry records older than 30 days. Run it periodically from a scheduled job to prevent unbounded table growth.

$this->journalCleanupFacade->execute();