refactor: one logging helper LIT\MCP\Log (M6)

Log::failure(context, detail) keeps the '[wp-lit-mcp] <context> failed: <detail>'
format of the nine former error_log() sites; Log::line() covers the change log line.
One phpcs ignore instead of eleven.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 17:36:01 +02:00
co-authored by Claude Opus 5.5
parent e005d83a3d
commit b6984d3f9a
11 changed files with 102 additions and 26 deletions
+4 -6
View File
@@ -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();
}
},
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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;
}
@@ -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(
@@ -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
@@ -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;
}
+2 -2
View File
@@ -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).'
+2 -2
View File
@@ -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 [];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace LIT\MCP;
use Throwable;
/**
* The plugin's one way into the PHP error log. Messages carry the "[wp-lit-mcp]" prefix; the
* log is for the site operator only and may contain details (paths, traces) that must never
* reach an MCP client.
*/
final class Log
{
private const PREFIX = '[wp-lit-mcp] ';
/** Logs "[wp-lit-mcp] <context> failed: <detail>"; 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
}
}
+2 -2
View File
@@ -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());
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit;
use LIT\MCP\Log;
use RuntimeException;
final class LogTest extends TestCase
{
/** @return string What the callback wrote to the error log. */
private static function captured(callable $write): string
{
// error_log() is a PHP internal and cannot be stubbed; redirect it to a temp file instead.
$log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
$previous = ini_set('error_log', $log);
try {
$write();
} finally {
ini_set('error_log', (string) $previous);
}
$content = (string) file_get_contents($log);
unlink($log);
return $content;
}
public function testFailureKeepsThePrefixedFormat(): void
{
$content = self::captured(static fn () => 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);
}
}