Skip to main content
Version: X-Cart 5.6.0

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:

LevelMethodPurpose
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

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