Security Event Logger
Writes normalized security events without exposing sensitive values.
Function signature
ogLogSecurityEvent(event_type, context = array(), options = array())
Categories
- Security
Parameters
event_typeShort event type such as login_failed or csrf_failed.contextEvent context to redact and normalize.optionsOptional severity, actor_id, ip_address, and log_path. Recognized keys: `actor_id`, `ip_address`, `log_path`, `severity`.Return value
Short public-safe status message.
- event
- written
Compatibility
Existing function name and call order preserved; metadata signature corrected to source.
Minimum PHP version: 7.4
Security notes
Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets out of public output.
Code
<?php
/*
* Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
* Free for personal and internal use. Paid project use requires visible credit
* to Jeffery L. Paris. Corporate use requires a paid license fee unless a
* separate written license states otherwise.
*/
/**
* Normalizes a security event for safe storage or file logging.
*
* The function redacts sensitive context keys before logging. If log_path is
* supplied in options, the path must point to an existing writable file or an
* existing writable directory. The function will not create arbitrary paths.
*
* @param string $event_type Short event type such as login_failed or csrf_failed.
* @param array $context Event context to redact and normalize.
* @param array $options Optional severity, actor_id, ip_address, and log_path.
* @return array Normalized event data and optional write status.
*/
function ogLogSecurityEvent($event_type, $context = array(), $options = array()) {
$result = array(
'success' => false,
'message' => '',
'data' => array()
);
$event_type = trim((string)$event_type);
if (empty($event_type)) {
$result['message'] = 'Missing security event type.';
return $result;
}
if (!preg_match('/^[a-zA-Z0-9_\-:.]+$/', $event_type)) {
$result['message'] = 'Security event type contains unsupported characters.';
return $result;
}
if (!is_array($context)) {
$context = array('raw_context' => (string)$context);
}
if (!is_array($options)) {
$options = array();
}
$severity = 'notice';
if (!empty($options['severity'])) {
$severity = strtolower(trim((string)$options['severity']));
}
$allowed_severities = array('debug', 'info', 'notice', 'warning', 'error', 'critical');
if (!in_array($severity, $allowed_severities, true)) {
$severity = 'notice';
}
$redacted_context = array();
foreach ($context as $key => $value) {
$key = (string)$key;
if (preg_match('/password|passwd|token|secret|api[_-]?key|authorization|cookie|csrf/i', $key)) {
$redacted_context[$key] = '[redacted]';
} elseif (is_scalar($value) || empty($value)) {
$redacted_context[$key] = $value;
} else {
$redacted_context[$key] = '[non-scalar context omitted]';
}
}
$event = array(
'event_type' => $event_type,
'severity' => $severity,
'created_at' => gmdate('c'),
'actor_id' => '',
'ip_address' => '',
'context' => $redacted_context
);
if (!empty($options['actor_id'])) {
$event['actor_id'] = (string)$options['actor_id'];
}
if (!empty($options['ip_address'])) {
$event['ip_address'] = (string)$options['ip_address'];
}
$written = false;
if (!empty($options['log_path'])) {
$log_path = (string)$options['log_path'];
$target_path = $log_path;
if (is_dir($log_path)) {
$target_path = rtrim($log_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'security.log';
}
$directory = dirname($target_path);
if (is_dir($directory) && is_writable($directory)) {
$line = json_encode($event) . PHP_EOL;
if ($line !== false) {
$written = (file_put_contents($target_path, $line, FILE_APPEND | LOCK_EX) !== false);
}
}
}
$result['success'] = true;
$result['message'] = 'Security event normalized.';
$result['data'] = array(
'event' => $event,
'written' => $written
);
return $result;
}