lthn.io/app/Core/Events/EventAuditLog.php
Claude 41a90cbff8
feat: lthn.io API serving live chain data
Fixed: basePath self→static binding, namespace detection, event wiring,
SQLite cache, file cache driver. All Mod Boot classes converted to
$listens pattern for lifecycle event discovery.

Working endpoints:
- /v1/explorer/info — live chain height, difficulty, aliases
- /v1/explorer/stats — formatted chain statistics
- /v1/names/directory — alias directory grouped by type
- /v1/names/available/{name} — name availability check
- /v1/names/lookup/{name} — name details

Co-Authored-By: Charon <charon@lethean.io>
2026-04-03 17:17:42 +01:00

269 lines
7.1 KiB
PHP

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
namespace Core\Events;
use Illuminate\Support\Facades\Log;
/**
* Tracks lifecycle event execution for debugging and monitoring.
*
* EventAuditLog records when lifecycle events fire and which handlers respond,
* including timing information and success/failure status. This is invaluable for:
*
* - **Debugging** - Understanding why modules aren't loading
* - **Performance** - Identifying slow event handlers
* - **Monitoring** - Tracking application bootstrap flow
* - **Diagnostics** - Finding failed handlers in production
*
* ## Enabling Audit Logging
*
* Logging is disabled by default for performance. Enable it when needed:
*
* ```php
* EventAuditLog::enable(); // Enable in-memory logging
* EventAuditLog::enableLog(); // Also write to Laravel log channel
* ```
*
* ## Retrieving Entries
*
* ```php
* $entries = EventAuditLog::entries(); // All entries
* $failures = EventAuditLog::failures(); // Only failed handlers
* $webEntries = EventAuditLog::entriesFor(WebRoutesRegistering::class);
* $summary = EventAuditLog::summary(); // Statistics
* ```
*
* ## Entry Structure
*
* Each entry contains:
* - `event` - Event class name
* - `handler` - Handler module class name
* - `duration_ms` - Execution time in milliseconds
* - `failed` - Whether the handler threw an exception
* - `error` - Error message (if failed)
* - `timestamp` - Unix timestamp with microseconds
*
* ## Integration with LazyModuleListener
*
* The `LazyModuleListener` automatically records to EventAuditLog when
* enabled, so you don't need to manually instrument event handlers.
*
*
* @see LazyModuleListener For automatic audit logging integration
*/
class EventAuditLog
{
private static bool $enabled = false;
private static bool $logEnabled = false;
/** @var array<int, array{event: string, handler: string, duration_ms: float, failed: bool, error?: string, timestamp: float}> */
private static array $entries = [];
/** @var array<string, float> */
private static array $pendingEvents = [];
/**
* Enable audit logging.
*/
public static function enable(): void
{
self::$enabled = true;
}
/**
* Disable audit logging.
*/
public static function disable(): void
{
self::$enabled = false;
}
/**
* Check if audit logging is enabled.
*/
public static function isEnabled(): bool
{
return self::$enabled;
}
/**
* Enable writing audit entries to Laravel log.
*/
public static function enableLog(): void
{
self::$logEnabled = true;
}
/**
* Disable writing audit entries to Laravel log.
*/
public static function disableLog(): void
{
self::$logEnabled = false;
}
/**
* Record the start of event handling.
*/
public static function recordStart(string $eventClass, string $handlerClass): void
{
if (! self::$enabled) {
return;
}
$key = "{$eventClass}:{$handlerClass}";
self::$pendingEvents[$key] = microtime(true);
}
/**
* Record successful completion of event handling.
*/
public static function recordSuccess(string $eventClass, string $handlerClass): void
{
if (! self::$enabled) {
return;
}
$key = "{$eventClass}:{$handlerClass}";
$startTime = self::$pendingEvents[$key] ?? microtime(true);
$duration = (microtime(true) - $startTime) * 1000;
unset(self::$pendingEvents[$key]);
$entry = [
'event' => $eventClass,
'handler' => $handlerClass,
'duration_ms' => round($duration, 2),
'failed' => false,
'timestamp' => microtime(true),
];
self::$entries[] = $entry;
if (self::$logEnabled) {
Log::debug('Lifecycle event handled', $entry);
}
}
/**
* Record a failed event handler.
*/
public static function recordFailure(string $eventClass, string $handlerClass, \Throwable $error): void
{
if (! self::$enabled) {
return;
}
$key = "{$eventClass}:{$handlerClass}";
$startTime = self::$pendingEvents[$key] ?? microtime(true);
$duration = (microtime(true) - $startTime) * 1000;
unset(self::$pendingEvents[$key]);
$entry = [
'event' => $eventClass,
'handler' => $handlerClass,
'duration_ms' => round($duration, 2),
'failed' => true,
'error' => $error->getMessage(),
'timestamp' => microtime(true),
];
self::$entries[] = $entry;
if (self::$logEnabled) {
Log::warning('Lifecycle event handler failed', $entry);
}
}
/**
* Get all recorded entries.
*
* @return array<int, array{event: string, handler: string, duration_ms: float, failed: bool, error?: string, timestamp: float}>
*/
public static function entries(): array
{
return self::$entries;
}
/**
* Get entries for a specific event class.
*
* @return array<int, array{event: string, handler: string, duration_ms: float, failed: bool, error?: string, timestamp: float}>
*/
public static function entriesFor(string $eventClass): array
{
return array_values(
array_filter(self::$entries, fn ($entry) => $entry['event'] === $eventClass)
);
}
/**
* Get only failed entries.
*
* @return array<int, array{event: string, handler: string, duration_ms: float, failed: bool, error: string, timestamp: float}>
*/
public static function failures(): array
{
return array_values(
array_filter(self::$entries, fn ($entry) => $entry['failed'])
);
}
/**
* Get summary statistics.
*
* @return array{total: int, failed: int, total_duration_ms: float, events: array<string, int>}
*/
public static function summary(): array
{
$eventCounts = [];
$totalDuration = 0.0;
$failedCount = 0;
foreach (self::$entries as $entry) {
$eventCounts[$entry['event']] = ($eventCounts[$entry['event']] ?? 0) + 1;
$totalDuration += $entry['duration_ms'];
if ($entry['failed']) {
$failedCount++;
}
}
return [
'total' => count(self::$entries),
'failed' => $failedCount,
'total_duration_ms' => round($totalDuration, 2),
'events' => $eventCounts,
];
}
/**
* Clear all recorded entries.
*/
public static function clear(): void
{
self::$entries = [];
self::$pendingEvents = [];
}
/**
* Reset to initial state (disable and clear).
*/
public static function reset(): void
{
self::$enabled = false;
self::$logEnabled = false;
self::clear();
}
}