diff --git a/README.md b/README.md index 0d26792..0e366bb 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,47 @@ A comprehensive WordPress logging service with Wonolog integration, secure file - **Zero Dependencies**: Works with or without external logging libraries - **Plugin Isolation**: Each plugin/theme gets independent log directories and settings +## Design Philosophy: Mixed Types for Maximum Flexibility + +WP Logger intentionally uses `mixed` type hints throughout its API, diverging from strict PSR Log ^2.0+ signatures. This design choice aligns with our primary integration target and WordPress ecosystem philosophy: + +### Why Mixed Types? + +**Wonolog Compatibility**: Our primary target, [Inpsyde Wonolog](https://github.com/inpsyde/Wonolog), accepts truly mixed types natively (Throwable, Arrays, Objects, WP_Error): +```php +// Wonolog native usage +do_action('wonolog.log', $exception); // ✅ Throwable +do_action('wonolog.log', ['data' => 'log']); // ✅ Array +do_action('wonolog.log', $wpError); // ✅ WP_Error +``` + +**PSR Log Version Differences**: +- **PSR Log ^1.0**: Uses `mixed` type hint but primarily expects strings +- **PSR Log ^2.0+**: Strictly enforces `string|\Stringable` only + +**WordPress Ecosystem**: WordPress prioritizes pragmatism and developer experience over strict typing. Our design reflects this philosophy. + +### Developer Benefits + +```php +// ✅ Natural and intuitive - all supported +$logger->error($exception); // Throwable objects +$logger->warning($wpError); // WP_Error objects +$logger->info(['user' => 123]); // Structured data +$logger->debug($customObject); // Any object + +// vs. ❌ Verbose PSR ^2.0+ requirement +$logger->error($exception->getMessage(), ['exception' => $exception]); +``` + +### How It Works + +- **With Wonolog**: Mixed types passed through natively without conversion +- **Fallback Mode**: Mixed types converted to strings via `formatMessage()` +- **Best of Both**: WordPress flexibility + PSR compatibility + Wonolog power + +This approach ensures zero breaking changes while maximizing compatibility across the WordPress ecosystem. + ## Installation Install via Composer: diff --git a/src/Logger.php b/src/Logger.php index 182d162..5a1ccf9 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -140,12 +140,69 @@ public function getConfig(): array } /** - * System is unusable. + * Logs with an arbitrary level with environment-aware behavior. + * + * MESSAGE TYPE COMPATIBILITY: + * + * - Wonolog (primary target): Accepts truly mixed types natively + * ✅ Throwable, WP_Error, Arrays, Objects, Strings - all supported + * + * - PSR Log ^1.0: Uses mixed type hint but expects primarily strings + * ⚠️ Throwable/Objects/Arrays not officially supported by spec + * + * - PSR Log ^2.0+: Strictly enforces string|\Stringable only + * ❌ Throwable, Arrays, Objects cause TypeError * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. + * DESIGN DECISION: + * WP Logger maintains Wonolog-compatible design for maximum flexibility + * in the WordPress ecosystem. When Wonolog is active, all mixed types + * are passed through natively. In fallback mode, types are converted + * to strings via formatMessage(). * - * @param mixed $message + * ACCEPTED TYPES: + * - Throwable objects: $logger->error($exception) + * - WP_Error objects: $logger->error($wpError) + * - Strings: $logger->error('message') + * - Arrays: $logger->error(['user' => 123]) + * - Objects: $logger->error($customObject) + * + * @param mixed $level + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) + * @param array $context + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = []): void + { + // Check if this log level should be recorded + if (!$this->shouldLog($level)) { + return; + } + + // Allow hook to override entire logging behavior + if (\function_exists('apply_filters')) { + $overrideResult = apply_filters('wp_logger_override_log', null, $level, $message, $context, $this->config); + if (null !== $overrideResult) { + return; // Custom logging handler took over + } + } + + if ($this->isWonologActive()) { + $this->logViaWonolog($level, $message, $context); + } else { + $this->logViaFallback($level, $message, $context); + } + + // Allow third-party plugins to hook into all logging + if (\function_exists('do_action')) { + do_action('wp_logger_logged', $level, $message, $context, $this->config['plugin_name']); + } + } + + /** + * System is unusable. + * + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function emergency($message, array $context = []): void { @@ -155,10 +212,7 @@ public function emergency($message, array $context = []): void /** * Action must be taken immediately. * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $message + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function alert($message, array $context = []): void { @@ -168,10 +222,7 @@ public function alert($message, array $context = []): void /** * Critical conditions. * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $message + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function critical($message, array $context = []): void { @@ -182,10 +233,7 @@ public function critical($message, array $context = []): void * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $message + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function error($message, array $context = []): void { @@ -195,10 +243,7 @@ public function error($message, array $context = []): void /** * Exceptional occurrences that are not errors. * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $message + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function warning($message, array $context = []): void { @@ -208,10 +253,7 @@ public function warning($message, array $context = []): void /** * Normal but significant events. * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $message + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function notice($message, array $context = []): void { @@ -221,10 +263,7 @@ public function notice($message, array $context = []): void /** * Interesting events. * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $message + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function info($message, array $context = []): void { @@ -234,55 +273,13 @@ public function info($message, array $context = []): void /** * Detailed debug information. * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $message + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ public function debug($message, array $context = []): void { $this->log(LogLevel::DEBUG, $message, $context); } - /** - * Logs with an arbitrary level with environment-aware behavior. - * - * NOTE: Type hints removed for PSR Log ^1.0 compatibility. - * TODO: Add back `string|\Stringable $message` type hint when PSR Log ^1.0 support is dropped. - * - * @param mixed $level - * @param mixed $message - * @param array $context - * - * @throws \Psr\Log\InvalidArgumentException - */ - public function log($level, $message, array $context = []): void - { - // Check if this log level should be recorded - if (!$this->shouldLog($level)) { - return; - } - - // Allow hook to override entire logging behavior - if (\function_exists('apply_filters')) { - $overrideResult = apply_filters('wp_logger_override_log', null, $level, $message, $context, $this->config); - if (null !== $overrideResult) { - return; // Custom logging handler took over - } - } - - if ($this->isWonologActive()) { - $this->logViaWonolog($level, $message, $context); - } else { - $this->logViaFallback($level, $message, $context); - } - - // Allow third-party plugins to hook into all logging - if (\function_exists('do_action')) { - do_action('wp_logger_logged', $level, $message, $context, $this->config['plugin_name']); - } - } - /** * Get Wonolog logger instance if available. */ @@ -497,11 +494,21 @@ private function getWonologNamespace(): string } /** - * Log via Wonolog with hook support. + * Log via Wonolog with native mixed type support. * + * Unlike PSR Log ^2.0+ (string|\Stringable only), Wonolog was designed + * for WordPress ecosystem flexibility and accepts truly mixed types: + * - Throwable objects (exceptions) + * - WP_Error objects + * - Arrays and Objects (structured data) + * - Strings and primitives + * + * This allows natural WordPress-style logging without type conversions. + * + * @param mixed $message - Accepts mixed types, conversion happens in formatMessage() * @param array $context */ - private function logViaWonolog(string $level, string|\Stringable $message, array $context): void + private function logViaWonolog(string $level, mixed $message, array $context): void { $wonologNamespace = $this->getWonologNamespace(); $logConstant = $wonologNamespace.'\LOG'; @@ -540,9 +547,10 @@ private function logViaWonolog(string $level, string|\Stringable $message, array /** * Fallback logging when Wonolog is not available with environment-aware behavior. * + * @param mixed $message - Accepts mixed types, conversion happens in formatMessage() * @param array $context */ - private function logViaFallback(string $level, string|\Stringable $message, array $context): void + private function logViaFallback(string $level, mixed $message, array $context): void { // Allow third-party plugins to handle logging when Wonolog is not available if (\function_exists('do_action')) { @@ -660,9 +668,10 @@ private function getLogDirectory(): string /** * Log to protected file in WordPress uploads directory. * + * @param mixed $message - Passes mixed message to formatLogEntry() for conversion * @param array $context */ - private function logToFile(string $level, string|\Stringable $message, array $context): void + private function logToFile(string $level, mixed $message, array $context): void { if (!\function_exists('wp_upload_dir') || !\function_exists('wp_mkdir_p')) { return; @@ -700,9 +709,10 @@ private function createLogDirectoryStructure(string $logDir): void /** * Format log entry for consistent output. * + * @param mixed $message - Calls formatMessage() to convert mixed → string * @param array $context */ - private function formatLogEntry(string $level, string|\Stringable $message, array $context): string + private function formatLogEntry(string $level, mixed $message, array $context): string { $timestamp = gmdate('Y-m-d H:i:s'); $environmentInfo = Environment::isProduction() ? '' : ' ['.Environment::getEnvironment().']'; @@ -760,9 +770,10 @@ private function maybeCleanupLogs(string $logDir): void /** * Traditional error_log (used in development/debug mode). * + * @param mixed $message - Calls formatMessage() to convert mixed → string * @param array $context */ - private function logToErrorLog(string $level, string|\Stringable $message, array $context): void + private function logToErrorLog(string $level, mixed $message, array $context): void { $environmentInfo = '['.Environment::getEnvironment().']'; @@ -786,9 +797,21 @@ private function logToErrorLog(string $level, string|\Stringable $message, array } /** - * Format message for consistent logging output. + * Format message for fallback logging (when Wonolog not available). + * + * Converts mixed types to strings for traditional logging methods. + * Only used in fallback mode - when Wonolog is active, mixed types + * are passed through natively without conversion. + * + * SUPPORTED CONVERSIONS: + * - Throwable → "Message in file.php:123" + * - WP_Error → error message string + * - Arrays/Objects → JSON representation + * - Primitives → string cast + * + * @param mixed $message - Accepts any type (Throwable, WP_Error, string, array, object) */ - private function formatMessage(string|\Stringable $message): string + private function formatMessage(mixed $message): string { // Handle Throwable objects if ($message instanceof \Throwable) {