Detection before damage: what, how, and when to log
Discovering a breach weeks after it happened means the decisive advantage is already gone. This article explains which security-relevant events actually need to be logged, how sensitive data like passwords and tokens stays protected while doing so, how central log aggregation makes anomalies visible, and how alert thresholds are set without exhausting the team with constant false positives.
Table of Contents
- 1. Why security logging determines detection
- 2. Which events are actually security-relevant
- 3. What must never end up in a log
- 4. Structured logging: format and context
- 5. Magento admin activity logging
- 6. Central log aggregation for anomaly detection
- 7. Detecting anomalies: patterns and correlation
- 8. Alerting thresholds without alert fatigue
- 9. Retention, access control, and compliance
- 10. Summary
- 11. FAQ
1. Why security logging determines detection
Most successful attacks are not discovered within minutes, but only after days or weeks, and often by a third party rather than through the organization's own monitoring. The gap between compromise and detection is exactly the window in which attackers exfiltrate data, plant backdoors, or capture payment data. Security logging closes that gap by capturing the events needed both for later reconstruction and for immediate alerting. A regular application log built for debugging is not enough for this, because it answers a different question: why a request failed, not who tried to gain unauthorized access.
For Magento stores, the admin panel and the checkout process are also favorite attack targets, especially for Magecart-style skimmers that capture payment data directly in the checkout. Without consistent security monitoring, a compromised admin account or a tampered payment module often stays undetected for months. Regulatory requirements such as PCI DSS requirement 10 or the GDPR's 72-hour breach notification duty also assume that reliable logs exist in the first place, ones from which the scope and timing of an incident can actually be reconstructed.
2. Which events are actually security-relevant
Not every event belongs in the security log, otherwise the relevant signal drowns in noise. Core categories are: authentication (successful and failed logins, logouts, password resets, multi-factor events), authorization (denied access to protected resources, attempts to reach admin routes with the wrong role), and administrative actions (configuration changes, creating or deleting user accounts, changes to payment or shipping methods). Each category carries a different attack signal: clustered login failures suggest brute forcing, repeated permission denials suggest privilege escalation attempts.
It's also worth logging input validation failures, which often indicate automated scans or targeted injection attempts, as well as API rate limit violations and unusual file uploads. What matters for every event is the same set of context fields: who acted, what was attempted, when, from which source, and with what outcome. Missing any one of these five building blocks makes the log significantly less useful for incident analysis, because events can no longer be clearly tied back to a cause.
<?php
declare(strict_types=1);
namespace Mironsoft\SecuritySuite\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;
/**
* Logs failed customer login attempts with structured context.
* Never logs the submitted password, only metadata needed for detection.
*/
class LogFailedLoginObserver implements ObserverInterface
{
/**
* @param LoggerInterface $securityLogger Dedicated security channel logger
*/
public function __construct(private readonly LoggerInterface $securityLogger)
{
}
/**
* Handles the customer_customer_authenticate_after event on failure.
*
* @param Observer $observer Event observer carrying request context
* @return void
*/
public function execute(Observer $observer): void
{
$email = (string) $observer->getEvent()->getData('email');
$exception = $observer->getEvent()->getData('exception');
if ($exception === null) {
return;
}
// Structured, machine-parseable context, no credentials included
$this->securityLogger->warning('auth.login.failed', [
'event_type' => 'authentication_failure',
'actor_email_hash' => hash('sha256', strtolower($email)),
'source_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'reason' => $exception->getMessage(),
'timestamp' => (new \DateTimeImmutable())->format(DATE_ATOM),
]);
}
}
3. What must never end up in a log
A log that contains plaintext passwords, full session tokens, API keys, or credit card numbers turns itself into an attack target: whoever gains access to the logs gets the credentials along with them. This pattern is catalogued as CWE-532 (Insertion of Sensitive Information into Log File) and is one of the most common mistakes in home-grown logging solutions. The rule is simple: anything that can be used for authentication, authorization, or payment must be masked, hashed, or omitted entirely, never stored in plain text.
In practice, a central redaction mechanism works best, one that doesn't need to be implemented manually at every single log call site but instead runs automatically before a log entry reaches its handler. A Monolog processor is the right place for this: it recursively walks context and extra data for known sensitive key names and replaces the values with a fixed mask. Correlation is preserved through a hashed or truncated identifier, while the original secret is never persisted at all.
<?php
declare(strict_types=1);
namespace Mironsoft\SecuritySuite\Logger\Processor;
/**
* Removes sensitive values from log records before they reach any handler.
* Runs as a Monolog processor, so redaction happens once, centrally, for
* every channel instead of relying on every call site to sanitize manually.
*/
class SensitiveDataProcessor
{
/** @var string[] Field names that must never appear in plain text */
private const REDACTED_KEYS = [
'password', 'passwd', 'pwd', 'token', 'access_token', 'refresh_token',
'authorization', 'secret', 'api_key', 'credit_card', 'cvv',
];
private const MASK = '***REDACTED***';
/**
* Processes a single log record and redacts sensitive keys recursively.
*
* @param array $record Monolog log record (message, context, extra, ...)
* @return array Sanitized log record safe to persist or ship
*/
public function __invoke(array $record): array
{
$record['context'] = $this->redact($record['context'] ?? []);
$record['extra'] = $this->redact($record['extra'] ?? []);
return $record;
}
/**
* Walks an array recursively and masks values whose key looks sensitive.
*
* @param array $data Arbitrary context data attached to a log entry
* @return array Data with sensitive values replaced by a fixed mask
*/
private function redact(array $data): array
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = $this->redact($value);
continue;
}
$normalizedKey = strtolower((string) $key);
foreach (self::REDACTED_KEYS as $sensitiveKey) {
if (str_contains($normalizedKey, $sensitiveKey)) {
$data[$key] = self::MASK;
continue 2;
}
}
}
return $data;
}
}
4. Structured logging: format and context
Free-text log lines like "Login failed for user X" are readable by humans but hard to process programmatically once millions of lines pile up per day. Structured logging in JSON format solves this, because every field can be queried, filtered, and correlated directly, without building fragile regex parsers. A consistent schema with fixed fields like timestamp, event_type, severity, actor, source_ip, and outcome makes log entries comparable across different systems, whether they come from the PHP backend, the web server, or a payment integration.
Just as important as the individual fields is a consistent correlation ID or trace ID, generated when a request comes in and passed through every downstream log entry, service, and asynchronous job. Only with that can a complete request, from a failed login through a subsequent access attempt on a protected resource to the eventual account lockout, be reconstructed as a coherent chain. Without a correlation ID, distributed systems remain a puzzle of isolated individual events during forensic analysis.
{
"timestamp": "2026-07-12T14:32:07Z",
"event_type": "authentication_failure",
"severity": "warning",
"actor": {
"type": "customer",
"id_hash": "9f3a1c2b7e4d5f6a8b9c0d1e2f3a4b5c",
"email_hash": "c1a5e2f8b3d4a9e7f6b2c1d0e9f8a7b6"
},
"source": {
"ip": "203.0.113.42",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
},
"context": {
"reason": "invalid_credentials",
"attempt_count": 3,
"account_locked": false
},
"trace_id": "b7e1f6a2-4c3d-4e5f-9a1b-2c3d4e5f6a7b",
"application": "magento",
"environment": "production"
}
5. Magento admin activity logging
Since Magento 2.4, the Magento_AdminActionsLog module ships a built-in log for admin activity, visible under System > Admin Actions Log. It captures logins, logouts, and basic configuration changes along with the admin user and timestamp. For many stores this is a solid baseline, but it logs generic framework events, not necessarily business-level actions such as changing a product price, creating a new admin account with the "Administrator" role, or disabling a payment method. Reaching that granular level requires custom plugins.
A plugin wrapping the controller's execute() method is the most reliable approach, because it works regardless of whether a module follows Magento's standard admin grid conventions or ships its own controllers. It's important to filter the logged request parameters before writing them, so no password fields or tokens end up there either. Combined with the admin user's ID, the executed action, and the source IP, this produces an audit trail that shows exactly what changed and when in the event of a compromised admin account.
<?php
declare(strict_types=1);
namespace Mironsoft\SecuritySuite\Plugin;
use Magento\Backend\App\Action;
use Magento\Backend\Model\Auth\Session;
use Psr\Log\LoggerInterface;
/**
* Logs every executed backend controller action for audit purposes.
* Wraps execute() instead of relying only on Magento's built-in admin log,
* so custom controllers (e.g. Mironsoft modules) are covered too.
*/
class LogAdminActionPlugin
{
/**
* @param LoggerInterface $securityLogger Dedicated security channel logger
* @param Session $authSession Backend auth session for the current admin user
*/
public function __construct(
private readonly LoggerInterface $securityLogger,
private readonly Session $authSession
) {
}
/**
* Logs the admin action before it is executed.
*
* @param Action $subject Backend controller being invoked
* @param callable $proceed Original execute() call
* @return mixed Result of the wrapped controller action
*/
public function aroundExecute(Action $subject, callable $proceed)
{
$adminUser = $this->authSession->getUser();
$request = $subject->getRequest();
$this->securityLogger->info('admin.action.executed', [
'event_type' => 'admin_action',
'admin_user_id' => $adminUser?->getId(),
'admin_username' => $adminUser?->getUsername(),
'action' => $request->getFullActionName(),
'params' => $this->filterParams($request->getParams()),
'source_ip' => $request->getClientIp(),
]);
return $proceed();
}
/**
* Removes sensitive request parameters before they are logged.
*
* @param array $params Raw request parameters
* @return array Parameters with sensitive keys removed
*/
private function filterParams(array $params): array
{
unset($params['password'], $params['current_password'], $params['token']);
return $params;
}
}
6. Central log aggregation for anomaly detection
A single server log is nearly useless for anomaly detection, because attacks rarely stay confined to one source. Only once PHP application logs, web server access logs, database audit logs, and WAF events flow into one place do patterns become visible that span multiple systems, for example a failed login followed by a successful login from a different IP address minutes later. Central log aggregation with tools like Filebeat, Fluentd, or Logstash collects logs from all sources, normalizes them, and pushes them into a searchable backend such as Elasticsearch, Loki, or a SIEM solution.
For Magento setups, this means that besides the PHP logs under var/log/, Nginx or Varnish access logs, MySQL slow query logs, and, where applicable, the logs of an upstream web application firewall should also be included. A log shipper like Filebeat reads files incrementally, detects rotation automatically, and tags every entry with metadata like hostname and environment before forwarding it. An encrypted connection to the central backend is essential, so the transport layer itself doesn't become the weak point.
# filebeat.yml - forward Magento security-relevant logs to Logstash
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/www/magento/var/log/security.log
- /var/www/magento/var/log/exception.log
- /var/www/magento/var/log/system.log
fields:
app: magento
environment: production
json.keys_under_root: true
json.add_error_key: true
multiline.pattern: '^\['
multiline.negate: true
multiline.match: after
output.logstash:
hosts: ["logstash.internal:5044"]
ssl.certificate_authorities: ["/etc/filebeat/ca.crt"]
processors:
- drop_fields:
fields: ["agent.ephemeral_id", "ecs.version"]
- add_host_metadata: {}
7. Detecting anomalies: patterns and correlation
Static rules such as "more than five failed logins" catch obvious brute force attempts, but fall short against slow, distributed attacks where an attacker spreads only one or two attempts per account across many IP addresses. What's needed in addition is a statistical baseline: how many logins per hour are normal for this store, which countries traffic typically comes from, at what times admin users usually work. Deviations from that baseline, such as an admin login from a country never seen before, are often more telling than any fixed threshold.
Correlating several weak signals into one strong one is especially effective: an "impossible travel" rule detects when the same user logs in from geographically distant locations within minutes, which is physically impossible and suggests a stolen session token or a compromised password. SIEM systems like Elastic Security or Wazuh ship pre-built correlation rules for such patterns, which can be combined with external signals like threat intelligence feeds for known malicious IP addresses.
8. Alerting thresholds without alert fatigue
Overly sensitive alerts lead a security team to reflexively dismiss every notification after a short time without even checking it, including the real incidents. This phenomenon is called alert fatigue and is one of the most common reasons attacks go undetected despite monitoring being in place. The fix lies in tiered severity levels instead of a single alarm state: informational events land on a dashboard, moderate anomalies create a ticket, and only clearly critical patterns, such as a successful login after ten failed attempts from a new IP, trigger an immediate notification with escalation.
Time windows and aggregation are the second lever: instead of alerting on every single failed login, counts are tracked over a rolling window, for example five failed attempts within 15 minutes per account or IP, and duplicates within a period are collapsed into a single alert. Thresholds should also be tuned regularly based on real incidents and false positives, rather than set once and never touched again. An alert without a clearly defined, documented response is just noise anyway, and belongs either sharpened or removed.
9. Retention, access control, and compliance
How long security logs must be retained is a trade-off between forensic usefulness and data protection: PCI DSS requires at least one year of retention for payment-relevant systems, with three months immediately available for analysis, while the GDPR's data minimization principle requires that personal data not be stored any longer than necessary. In practice this often means a tiered strategy: complete, searchable logs kept short-term for active investigations, aggregated or anonymized data kept long-term for trend analysis.
Access to security logs themselves must be protected under the principle of least privilege, since whoever can alter or delete logs can erase the traces of an attack. Immutable or WORM storage (Write Once, Read Many) prevents subsequent modification, and separating access rights between the teams that generate logs and those that analyze them reduces the risk of an insider attack. Anyone aggregating logs centrally should therefore treat the aggregation layer itself as a critical system, with its own audit log for access to the logs.
Logging security-relevant events in a structured way is one half of the task, reliably keeping sensitive data out of those same logs is the other. The table below compares naive logging practice against the recommended, secure approach.
| Data type / event | Wrong approach | Correct approach | Risk if mishandled |
|---|---|---|---|
| Login password | Plaintext in error/debug log | Log only outcome and hashed identifier | Full credential leak on log access |
| Session/API token | Logging the full token | Mask it or log only a prefix | Session hijacking on log compromise |
| Payment data | Card number in a debug log | Never log it, only the transaction ID | PCI DSS violation, fines, loss of trust |
| Failed logins | Only a counter, no context | Structured with IP, user agent, timestamp | Attack patterns undetectable without context |
| Admin actions | Config changes stay unlogged | Log admin ID, action, and entity in structure | No audit trail during incident response |
Mironsoft
Security audits, logging architecture, and monitoring setup for Magento stores
Ready for security monitoring that actually catches attacks?
We build structured security logging, central log aggregation, and well-tuned alert thresholds for your Magento store, including admin activity logging and a redaction strategy that reliably keeps sensitive data out of every log.
Logging audit
Review existing logs for security gaps and sensitive data exposure
Aggregation setup
Build Filebeat/Fluentd pipelines and a central log backend
Alerting tuning
Define thresholds and reduce alert fatigue across the team
10. Summary
Security logging and monitoring address a single core problem: keeping the time between compromise and detection as short as possible. Authentication events, authorization violations, and administrative actions form the core of what must be logged, each with full context on actor, source, timing, and outcome. Passwords, tokens, and payment data must never appear in plaintext in a log; a central redaction mechanism such as a Monolog processor enforces that rule automatically, rather than relying on manual discipline at every log call site.
Only central log aggregation makes patterns visible across multiple systems, from impossible-travel logins to distributed brute force attempts. For this signal not to drown in noise, it needs tiered alert levels, time-window aggregation, and regularly retuned thresholds; otherwise alert fatigue causes real incidents to get lost in the flood of notifications. Magento's built-in Admin Actions Log is a solid starting point, but it doesn't replace a dedicated, fine-grained audit trail for critical business actions.
Security Logging and Monitoring - The Essentials at a Glance
What to log
Auth events, permission denials, and admin actions, captured in structure with actor, source, timing, and outcome.
What not to log
Passwords, full tokens, and payment data never in plaintext, masked centrally via a Monolog processor.
Central aggregation
Filebeat/Fluentd collect PHP, web server, and database logs for cross-system correlation.
Alerting without fatigue
Tiered severity levels, time-window aggregation, and thresholds retuned on a regular basis.