diff --git a/src/Abilities/AbilityRegistrar.php b/src/Abilities/AbilityRegistrar.php index 439044a..0e84f0e 100644 --- a/src/Abilities/AbilityRegistrar.php +++ b/src/Abilities/AbilityRegistrar.php @@ -6,6 +6,7 @@ namespace LIT\MCP\Abilities; use LIT\MCP\Access; use LIT\MCP\Integrations\IntegrationRegistry; +use LIT\MCP\Log; use LIT\MCP\Server\RequestContext; use Throwable; use WP_Error; @@ -64,8 +65,7 @@ final class AbilityRegistrar try { return $ability->permission(is_array($input) ? $input : []); } catch (Throwable $e) { - $message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure($ability->name(), $e); return Errors::internal(); } }, @@ -73,12 +73,10 @@ final class AbilityRegistrar try { return $ability->execute(is_array($input) ? $input : []); } catch (WriteFailed $e) { - $message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure($ability->name(), $e); return $e->toError(); } catch (Throwable $e) { - $message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure($ability->name(), $e); return Errors::internal(); } }, diff --git a/src/Abilities/FollowUp.php b/src/Abilities/FollowUp.php index 4c641bb..039f79b 100644 --- a/src/Abilities/FollowUp.php +++ b/src/Abilities/FollowUp.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace LIT\MCP\Abilities; +use LIT\MCP\Log; use Throwable; /** @@ -20,8 +21,7 @@ final class FollowUp try { return $step(); } catch (Throwable $e) { - $message = sprintf('[wp-lit-mcp] %s failed: %s', $context, $e); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure($context, $e); $warnings[] = $warning; return null; diff --git a/src/ChangeLog/ChangeLog.php b/src/ChangeLog/ChangeLog.php index ae0e712..574c2db 100644 --- a/src/ChangeLog/ChangeLog.php +++ b/src/ChangeLog/ChangeLog.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace LIT\MCP\ChangeLog; use LIT\MCP\Abilities\FollowUp; +use LIT\MCP\Log; use LIT\MCP\Server\RequestContext; /** @@ -57,8 +58,7 @@ class ChangeLog ); if ($inserted === false) { - $message = sprintf('[wp-lit-mcp] change log insert failed for %s %s:%s', $ability, $objectType, $objectId); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::line(sprintf('change log insert failed for %s %s:%s', $ability, $objectType, $objectId)); return null; } diff --git a/src/Integrations/Divi/Pages/DiviPageLoader.php b/src/Integrations/Divi/Pages/DiviPageLoader.php index 1306dc6..08d06f1 100644 --- a/src/Integrations/Divi/Pages/DiviPageLoader.php +++ b/src/Integrations/Divi/Pages/DiviPageLoader.php @@ -10,6 +10,7 @@ use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex; use LIT\MCP\Integrations\Divi\BlockTree\Parser; use LIT\MCP\Integrations\WordPress\PostGuard; use LIT\MCP\Integrations\WordPress\Schema\ContentSchema; +use LIT\MCP\Log; use WP_Error; use WP_Post; @@ -51,8 +52,7 @@ class DiviPageLoader try { $document = $this->parser->parse($content); } catch (MalformedMarkup $e) { - $message = sprintf('[wp-lit-mcp] divi parse of post %d failed: %s', $post->ID, $e->getMessage()); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure(sprintf('divi parse of post %d', $post->ID), $e->getMessage()); return Errors::unsupported( sprintf( diff --git a/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php b/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php index fdfd2b9..e37b66f 100644 --- a/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php +++ b/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php @@ -15,6 +15,7 @@ use LIT\MCP\Integrations\Divi\BlockTree\Serializer; use LIT\MCP\Integrations\Divi\Compose\TextReplacer; use LIT\MCP\Integrations\WordPress\PostGuard; use LIT\MCP\Integrations\WordPress\Schema\ContentSchema; +use LIT\MCP\Log; use Throwable; use WP_Error; @@ -484,8 +485,7 @@ final class DiviReplaceTextAbility implements Ability private static function log(int $postId, Throwable $e): void { - $message = sprintf('[wp-lit-mcp] lit/divi-replace-text on post %d failed: %s', $postId, $e); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure(sprintf('lit/divi-replace-text on post %d', $postId), $e); } private function db(): \wpdb diff --git a/src/Integrations/Divi/Pages/DynamicContentMap.php b/src/Integrations/Divi/Pages/DynamicContentMap.php index aaea737..369aa72 100644 --- a/src/Integrations/Divi/Pages/DynamicContentMap.php +++ b/src/Integrations/Divi/Pages/DynamicContentMap.php @@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi\Pages; use Closure; use LIT\MCP\Integrations\Divi\Compose\Placeholders; use LIT\MCP\Integrations\Divi\Compose\VariableRef; +use LIT\MCP\Log; use Throwable; /** @@ -43,12 +44,7 @@ class DynamicContentMap try { $options = $this->registry !== null ? ($this->registry)($postId) : self::divi($postId); } catch (Throwable $e) { - $message = sprintf( - '[wp-lit-mcp] divi dynamic content options of post %d failed: %s', - $postId, - $e->getMessage() - ); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure(sprintf('divi dynamic content options of post %d', $postId), $e->getMessage()); return null; } diff --git a/src/Integrations/Media/MediaImporter.php b/src/Integrations/Media/MediaImporter.php index 08b49a3..fc0838a 100644 --- a/src/Integrations/Media/MediaImporter.php +++ b/src/Integrations/Media/MediaImporter.php @@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Media; use LIT\MCP\Abilities\Errors; use LIT\MCP\Abilities\WriteFailed; use LIT\MCP\Limits; +use LIT\MCP\Log; use Throwable; use WP_Error; @@ -197,8 +198,7 @@ class MediaImporter ); if ($attachmentId instanceof WP_Error) { // WordPress' message can contain server paths: only for the log, not for the client. - $message = sprintf('[wp-lit-mcp] media sideload failed: %s', $attachmentId->get_error_message()); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure('media sideload', $attachmentId->get_error_message()); return Errors::writeFailed( 'WordPress hat die Datei nicht übernommen (z. B. Upload-Verzeichnis nicht beschreibbar).' diff --git a/src/Integrations/Media/MediaUsageFinder.php b/src/Integrations/Media/MediaUsageFinder.php index ec10818..9340092 100644 --- a/src/Integrations/Media/MediaUsageFinder.php +++ b/src/Integrations/Media/MediaUsageFinder.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace LIT\MCP\Integrations\Media; use Closure; +use LIT\MCP\Log; /** * Finds where an attachment is used (spec §6.3). Deliberately errs on the side of "used": @@ -165,8 +166,7 @@ class MediaUsageFinder } $this->queryFailed = true; - $message = sprintf('[wp-lit-mcp] media usage query failed: %s', (string) $wpdb->last_error); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure('media usage query', (string) $wpdb->last_error); return []; } diff --git a/src/Log.php b/src/Log.php new file mode 100644 index 0000000..9a79ed3 --- /dev/null +++ b/src/Log.php @@ -0,0 +1,29 @@ + failed: "; a Throwable is logged with its trace. */ + public static function failure(string $context, Throwable|string $detail): void + { + self::line(sprintf('%s failed: %s', $context, (string) $detail)); + } + + /** Logs one free-form line with the plugin prefix. */ + public static function line(string $message): void + { + error_log(self::PREFIX . $message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + } +} diff --git a/src/Server/ServerRegistrar.php b/src/Server/ServerRegistrar.php index 7516852..5df84a2 100644 --- a/src/Server/ServerRegistrar.php +++ b/src/Server/ServerRegistrar.php @@ -7,6 +7,7 @@ namespace LIT\MCP\Server; use LIT\MCP\Abilities\ExposurePolicy; use LIT\MCP\Guides\InstructionsBuilder; use LIT\MCP\Integrations\IntegrationRegistry; +use LIT\MCP\Log; use LIT\MCP\Plugin; use WP\MCP\Core\McpAdapter; use WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler; @@ -131,8 +132,7 @@ final class ServerRegistrar $result = ($this->createAdapterServer)($args); if (is_wp_error($result)) { - $message = '[wp-lit-mcp] MCP server registration failed: ' . $result->get_error_message(); - error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + Log::failure('MCP server registration', $result->get_error_message()); } } diff --git a/tests/Unit/LogTest.php b/tests/Unit/LogTest.php new file mode 100644 index 0000000..d4bf86e --- /dev/null +++ b/tests/Unit/LogTest.php @@ -0,0 +1,53 @@ + Log::failure('media usage query', 'Table gone')); + + self::assertStringContainsString('[wp-lit-mcp] media usage query failed: Table gone', $content); + } + + public function testFailureLogsAThrowableWithItsTrace(): void + { + $e = new RuntimeException('kaputt'); + $content = self::captured(static fn () => Log::failure('lit/x', $e)); + + self::assertStringContainsString('[wp-lit-mcp] lit/x failed: RuntimeException: kaputt', $content); + self::assertStringContainsString('Stack trace', $content); + } + + public function testLineOnlyAddsThePrefix(): void + { + $content = self::captured(static fn () => Log::line('change log insert failed for a b:1')); + + self::assertStringContainsString('[wp-lit-mcp] change log insert failed for a b:1', $content); + } +}