Skip to content
← Back to Functions
Code

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;
}