| 1 |
<?php |
| 2 |
|
| 3 |
namespace Pushly\Admin; |
| 4 |
|
| 5 |
if ( ! defined( 'ABSPATH' ) ) { |
| 6 |
exit; |
| 7 |
} |
| 8 |
|
| 9 |
class LogWriter { |
| 10 |
|
| 11 |
private const ALLOWED_SEVERITIES = [ 'debug', 'info', 'warning', 'error' ]; |
| 12 |
|
| 13 |
/** @var array<int, array{timestamp: string, severity: string, event_type: string, message: string, post_id: ?int, context: ?string}> */ |
| 14 |
private array $buffer = []; |
| 15 |
|
| 16 |
private LogStore $store; |
| 17 |
|
| 18 |
private bool $enabled; |
| 19 |
|
| 20 |
/** |
| 21 |
* @param LogStore $store The database storage layer for log entries. |
| 22 |
* @param bool $enabled Whether debug logging is active. |
| 23 |
*/ |
| 24 |
public function __construct( LogStore $store, bool $enabled ) { |
| 25 |
$this->store = $store; |
| 26 |
$this->enabled = $enabled; |
| 27 |
} |
| 28 |
|
| 29 |
/** |
| 30 |
* Registers the shutdown hook to flush buffered entries. |
| 31 |
*/ |
| 32 |
public function register_hooks(): void { |
| 33 |
add_action( 'shutdown', [ $this, 'flush' ] ); |
| 34 |
} |
| 35 |
|
| 36 |
/** |
| 37 |
* Adds a log entry to the in-memory buffer. |
| 38 |
* No-ops when debug logging is disabled or severity is invalid. |
| 39 |
* |
| 40 |
* @param string $severity One of: debug, info, warning, error. |
| 41 |
* @param string $event_type Machine-readable event identifier. |
| 42 |
* @param string $message Human-readable message. |
| 43 |
* @param int|null $post_id Associated post ID, if applicable. |
| 44 |
* @param array|null $context Additional structured data. |
| 45 |
*/ |
| 46 |
public function log( string $severity, string $event_type, string $message, ?int $post_id = null, ?array $context = null ): void { |
| 47 |
if ( ! $this->enabled ) { |
| 48 |
return; |
| 49 |
} |
| 50 |
|
| 51 |
if ( ! in_array( $severity, self::ALLOWED_SEVERITIES, true ) ) { |
| 52 |
return; |
| 53 |
} |
| 54 |
|
| 55 |
$this->buffer[] = [ |
| 56 |
'timestamp' => gmdate( 'Y-m-d H:i:s' ), |
| 57 |
'severity' => $severity, |
| 58 |
'event_type' => $event_type, |
| 59 |
'message' => $message, |
| 60 |
'post_id' => $post_id, |
| 61 |
'context' => $context !== null ? wp_json_encode( $context ) : null, |
| 62 |
]; |
| 63 |
} |
| 64 |
|
| 65 |
/** |
| 66 |
* Writes a single entry directly to the store, bypassing the buffer. |
| 67 |
* Used for environment snapshots that must persist immediately. |
| 68 |
* |
| 69 |
* @param string $severity One of: debug, info, warning, error. |
| 70 |
* @param string $event_type Machine-readable event identifier. |
| 71 |
* @param string $message Human-readable message. |
| 72 |
* @param int|null $post_id Associated post ID, if applicable. |
| 73 |
* @param array|null $context Additional structured data. |
| 74 |
*/ |
| 75 |
public function log_immediate( string $severity, string $event_type, string $message, ?int $post_id = null, ?array $context = null ): void { |
| 76 |
$entry = [ |
| 77 |
'timestamp' => gmdate( 'Y-m-d H:i:s' ), |
| 78 |
'severity' => $severity, |
| 79 |
'event_type' => $event_type, |
| 80 |
'message' => $message, |
| 81 |
'post_id' => $post_id, |
| 82 |
'context' => $context !== null ? wp_json_encode( $context ) : null, |
| 83 |
]; |
| 84 |
|
| 85 |
$this->store->insert_batch( [ $entry ] ); |
| 86 |
} |
| 87 |
|
| 88 |
/** |
| 89 |
* Flushes all buffered entries to the LogStore in a single batch. |
| 90 |
* Called on the WordPress shutdown action. |
| 91 |
* Catches all exceptions silently to ensure logging never interrupts the request. |
| 92 |
* Clears the buffer regardless of success or failure. |
| 93 |
*/ |
| 94 |
public function flush(): void { |
| 95 |
if ( empty( $this->buffer ) ) { |
| 96 |
return; |
| 97 |
} |
| 98 |
|
| 99 |
try { |
| 100 |
$this->store->insert_batch( $this->buffer ); |
| 101 |
} catch ( \Exception $e ) { |
| 102 |
// Intentionally silent — logging must never cause a visible error. |
| 103 |
} |
| 104 |
|
| 105 |
$this->buffer = []; |
| 106 |
} |
| 107 |
|
| 108 |
/** |
| 109 |
* Returns whether debug logging is enabled. |
| 110 |
*/ |
| 111 |
public function is_enabled(): bool { |
| 112 |
return $this->enabled; |
| 113 |
} |
| 114 |
} |
| 115 |
|