Logging in X-Cart 5.6
X-Cart 5.6 uses Monolog for logging via the PSR-3 LoggerInterface. This allows flexible logging configuration for different parts of the system and modules.
Logging Basics
Log Levels
Monolog supports standard PSR-3 log levels:
| Level | Method | Purpose |
|---|---|---|
| DEBUG | $logger->debug() | Detailed debugging information |
| INFO | $logger->info() | Informational messages about system operation |
| WARNING | $logger->warning() | Warnings about potential issues |
| ERROR | $logger->error() | Errors that do not cause the system to stop |
| CRITICAL | $logger->critical() | Critical errors |
| ALERT | $logger->alert() | Critical system errors requiring immediate intervention |
| EMERGENCY | $logger->emergency() | System emergency situations |
Using Context
All logging methods accept a second parameter — a context array that allows passing additional data:
$logger->debug('Processing order', [
'order_id' => $order->getOrderId(),
'total' => $order->getTotal(),
'status' => $order->getPaymentStatus()
]);
Ways to Obtain a Logger
1. Dependency Injection (Recommended)
For new services and classes that support DI, use constructor injection:
namespace XC\MyModule\Core;
use Psr\Log\LoggerInterface;
class MyService
{
public function __construct(
protected LoggerInterface $logger
) {
}
public function doSomething(): void
{
$this->logger->info('Performing operation');
}
}
2. Service Locator (for Legacy Code)
For classes that do not support DI (e.g., legacy models or controllers):
use XLite\Core\Container;
use Psr\Log\LoggerInterface;
class MyClass extends \XLite\Base\SuperClass
{
protected LoggerInterface $logger;
public function __construct()
{
parent::__construct();
$this->logger = Container::getContainer()->get('monolog.logger');
}
}
3. Via XLite\Logger
For quick solutions and backward compatibility:
\XLite\Logger::getLogger('MyModule-Component')->error('Operation execution error');
4. InjectLoggerTrait
For singletons and base classes, you can use the trait:
use XLite\Core\InjectLoggerTrait;
class MyClass extends \XLite\Base\Singleton
{
use InjectLoggerTrait;
public function doSomething(): void
{
$this->getLogger()->info('Operation completed');
}
}
Named Loggers
To separate logs by module or component, use named loggers.
Creating a Named Logger
In the modules/[Author]/[Name]/config/services.yaml file:
services:
xc.mymodule.logger:
class: Psr\Log\LoggerInterface
public: true
factory: ['@XLite\Logger', getLogger]
arguments:
- 'XC-MyModule' # Logger channel name
Using with Autowiring
services:
XC\MyModule\Core\MyService:
arguments:
$logger: '@xc.mymodule.logger'
Sub-loggers
To create sub-loggers with a more specific name, use the withName() method:
$logger = Container::getContainer()
->get('monolog.logger.payments')
->withName('CDev-Paypal');
Payment Method Logging
A dedicated service monolog.logger.payments is used for logging in payment modules. This allows logging to be independent of the main application logging level and provides centralized management of payment logs.
Obtaining the Payment Logger via DI
namespace CDev\MyPayment\Core;
use Psr\Log\LoggerInterface;
class PaymentProcessor
{
public function __construct(
protected LoggerInterface $paymentsLogger // Will be autowired to monolog.logger.payments
) {
}
public function processPayment(): void
{
$this->paymentsLogger->debug('Processing payment', [
'transaction_id' => $transaction->getTransactionId(),
'amount' => $transaction->getValue()
]);
}
}
Obtaining the Payment Logger via Service Locator
use XLite\Core\Container;
class LegacyPaymentProcessor extends \XLite\Base\SuperClass
{
protected LoggerInterface $logger;
public function __construct()
{
parent::__construct();
$logger = Container::getContainer()
->get('monolog.logger.payments')
->withName('XC-MyPayment');
$this->logger = $logger;
}
}
Sensitive Data Masking
Available since version 5.6.0.7
When logging payment and API data, always mask sensitive information using SensitiveDataMaskingProcessor. This processor supports flexible masking patterns with wildcards.
Basic Usage
use XCart\Monolog\Processor\SensitiveDataMaskingProcessor;
$this->logger->debug('Sending payment gateway request', [
'method' => 'POST',
'url' => $url,
'credentials' => [
'api_key' => 'secret_key_123',
'password' => 'user_password'
],
'payment_data' => [
'card_number' => '4111111111111111',
'cvv' => '123'
],
SensitiveDataMaskingProcessor::MASK_PATHS => [
'credentials.password',
'credentials.api_key',
'payment_data.card_number',
'payment_data.cvv'
]
]);
Pattern Syntax
- Dot notation:
credentials.password— masks the exact field - Single wildcard:
users.*.password— masks the field at one nesting level - Double wildcard:
**.password— masks the field at ANY depth recursively (not recommended due to performance, use specific paths instead)
Pattern Examples
// Recommended: specific paths
SensitiveDataMaskingProcessor::MASK_PATHS => [
'credentials.api_key',
'credentials.password',
'payment.card_number',
'payment.cvv'
]
// Single wildcard for arrays
SensitiveDataMaskingProcessor::MASK_PATHS => [
'users.*.password',
'users.*.api_key',
'items.*.secret_token'
]
// Combined patterns
SensitiveDataMaskingProcessor::MASK_PATHS => [
'credentials.password',
'credentials.api_key',
'payment.card_number',
'users.*.token'
]
Custom Mask Pattern
You can change the masking characters:
$this->logger->debug('Payment info', [
'card_number' => '4111111111111111',
SensitiveDataMaskingProcessor::MASK_PATHS => ['card_number'],
SensitiveDataMaskingProcessor::MASK_PATTERN => '[REDACTED]'
]);
// Result: card_number: '[REDACTED]'
The default mask pattern is ***MASKED***.
Webhook Logging
For payment webhooks, it is recommended to log both the incoming data and the processing result:
public function processWebhook($data): void
{
$this->logger->debug('Webhook received', [
'event_type' => $data['type'] ?? 'unknown',
'event_id' => $data['id'] ?? null,
'data' => $data,
SensitiveDataMaskingProcessor::MASK_PATHS => [
'data.api_key',
'data.access_token',
'data.password',
'data.card.number',
'data.card.cvv'
]
]);
try {
// Process webhook
$result = $this->handleEvent($data);
$this->logger->info('Webhook processed successfully', [
'event_id' => $data['id']
]);
} catch (\Exception $e) {
$this->logger->error('Webhook processing error: ' . $e->getMessage(), [
'event_id' => $data['id'],
'exception' => get_class($e)
]);
throw $e;
}
}
Custom Logging Channels
For modules with intensive logging, it is recommended to create separate channels and log files.
Channel Configuration
In the modules/[Author]/[Name]/config/services.yaml file:
monolog:
channels:
- mymodule_operations # Channel name
handlers:
mymodule_operations:
level: debug
type: service
id: xc.mymodule.logs_handler
channels: ["mymodule_operations"]
services:
xc.mymodule.logs_formatter:
class: Monolog\Formatter\LineFormatter
arguments:
$format: "[%%datetime%%] %%level_name%%: %%message%% %%context%%\n"
xc.mymodule.logs_handler:
class: XCart\Monolog\Handler\XCFileHandler
calls:
- [setFormatter, ['@xc.mymodule.logs_formatter']]
arguments:
$path: '%kernel.logs_dir%'
$file: 'mymodule' # File name without extension
$level: '100' # DEBUG level
xc.mymodule.logger:
class: Psr\Log\LoggerInterface
public: true
factory: ['@XLite\Logger', getLogger]
arguments:
- 'XC-MyModule'
Module Usage Examples
External API Integration
namespace XC\MyModule\Core\API;
use Psr\Log\LoggerInterface;
use XCart\Monolog\Processor\SensitiveDataMaskingProcessor;
class ApiClient
{
public function __construct(
protected LoggerInterface $logger
) {
}
public function sendRequest(string $method, string $endpoint, array $data = []): array
{
$this->logger->debug("Sending API request: $endpoint", [
'method' => $method,
'data' => $data,
SensitiveDataMaskingProcessor::MASK_PATHS => [
'data.api_key',
'data.access_token',
'data.password',
'data.client_secret'
]
]);
try {
$response = $this->httpClient->request($method, $endpoint, $data);
$this->logger->debug("API response received: $endpoint", [
'status_code' => $response->getStatusCode(),
'response' => $response->toArray(),
SensitiveDataMaskingProcessor::MASK_PATHS => [
'response.access_token',
'response.refresh_token'
]
]);
return $response->toArray();
} catch (\Exception $e) {
$this->logger->error("API request error: $endpoint", [
'method' => $method,
'exception' => get_class($e),
'message' => $e->getMessage()
]);
throw $e;
}
}
}
Tax Module with External Service
namespace XC\MyTax\Core;
use Psr\Log\LoggerInterface;
class TaxCalculator
{
public function __construct(
protected LoggerInterface $logger
) {
}
public function calculateTax(Order $order): float
{
$this->logger->debug('Calculating tax for order', [
'order_id' => $order->getOrderId(),
'total' => $order->getTotal(),
'address' => $order->getProfile()->getBillingAddress()->getState()
]);
$taxAmount = $this->performCalculation($order);
$this->logger->info('Tax calculated', [
'order_id' => $order->getOrderId(),
'tax_amount' => $taxAmount
]);
return $taxAmount;
}
}
Best Practices
What to Log
Recommended:
- Start and completion of important operations
- External API requests and responses
- Errors and exceptions
- Changes to critical data (orders, payments, users)
- Results of asynchronous tasks
- Security events (failed login attempts, permission changes)
Not recommended:
- Sensitive data without masking (passwords, tokens, card numbers)
- Excessive logging in loops
- User personal data without necessity
- Binary data
Naming Conventions
- Use clear channel names:
XC-ModuleName - Use meaningful messages: instead of "Error", write "Error saving order #123"
- Group related logs under a single channel