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.
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.
$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: error → error, warning → warning, 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, orsecret
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.