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:
@@ -6,6 +6,7 @@ namespace LIT\MCP\Abilities;
|
|||||||
|
|
||||||
use LIT\MCP\Access;
|
use LIT\MCP\Access;
|
||||||
use LIT\MCP\Integrations\IntegrationRegistry;
|
use LIT\MCP\Integrations\IntegrationRegistry;
|
||||||
|
use LIT\MCP\Log;
|
||||||
use LIT\MCP\Server\RequestContext;
|
use LIT\MCP\Server\RequestContext;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
@@ -64,8 +65,7 @@ final class AbilityRegistrar
|
|||||||
try {
|
try {
|
||||||
return $ability->permission(is_array($input) ? $input : []);
|
return $ability->permission(is_array($input) ? $input : []);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e);
|
Log::failure($ability->name(), $e);
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
return Errors::internal();
|
return Errors::internal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -73,12 +73,10 @@ final class AbilityRegistrar
|
|||||||
try {
|
try {
|
||||||
return $ability->execute(is_array($input) ? $input : []);
|
return $ability->execute(is_array($input) ? $input : []);
|
||||||
} catch (WriteFailed $e) {
|
} catch (WriteFailed $e) {
|
||||||
$message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e);
|
Log::failure($ability->name(), $e);
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
return $e->toError();
|
return $e->toError();
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e);
|
Log::failure($ability->name(), $e);
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
return Errors::internal();
|
return Errors::internal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace LIT\MCP\Abilities;
|
namespace LIT\MCP\Abilities;
|
||||||
|
|
||||||
|
use LIT\MCP\Log;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,8 +21,7 @@ final class FollowUp
|
|||||||
try {
|
try {
|
||||||
return $step();
|
return $step();
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$message = sprintf('[wp-lit-mcp] %s failed: %s', $context, $e);
|
Log::failure($context, $e);
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
$warnings[] = $warning;
|
$warnings[] = $warning;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\ChangeLog;
|
namespace LIT\MCP\ChangeLog;
|
||||||
|
|
||||||
use LIT\MCP\Abilities\FollowUp;
|
use LIT\MCP\Abilities\FollowUp;
|
||||||
|
use LIT\MCP\Log;
|
||||||
use LIT\MCP\Server\RequestContext;
|
use LIT\MCP\Server\RequestContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,8 +58,7 @@ class ChangeLog
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($inserted === false) {
|
if ($inserted === false) {
|
||||||
$message = sprintf('[wp-lit-mcp] change log insert failed for %s %s:%s', $ability, $objectType, $objectId);
|
Log::line(sprintf('change log insert failed for %s %s:%s', $ability, $objectType, $objectId));
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
|||||||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||||||
use LIT\MCP\Integrations\WordPress\PostGuard;
|
use LIT\MCP\Integrations\WordPress\PostGuard;
|
||||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||||
|
use LIT\MCP\Log;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
use WP_Post;
|
use WP_Post;
|
||||||
|
|
||||||
@@ -51,8 +52,7 @@ class DiviPageLoader
|
|||||||
try {
|
try {
|
||||||
$document = $this->parser->parse($content);
|
$document = $this->parser->parse($content);
|
||||||
} catch (MalformedMarkup $e) {
|
} catch (MalformedMarkup $e) {
|
||||||
$message = sprintf('[wp-lit-mcp] divi parse of post %d failed: %s', $post->ID, $e->getMessage());
|
Log::failure(sprintf('divi parse of post %d', $post->ID), $e->getMessage());
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
|
|
||||||
return Errors::unsupported(
|
return Errors::unsupported(
|
||||||
sprintf(
|
sprintf(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use LIT\MCP\Integrations\Divi\BlockTree\Serializer;
|
|||||||
use LIT\MCP\Integrations\Divi\Compose\TextReplacer;
|
use LIT\MCP\Integrations\Divi\Compose\TextReplacer;
|
||||||
use LIT\MCP\Integrations\WordPress\PostGuard;
|
use LIT\MCP\Integrations\WordPress\PostGuard;
|
||||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||||
|
use LIT\MCP\Log;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
|
||||||
@@ -484,8 +485,7 @@ final class DiviReplaceTextAbility implements Ability
|
|||||||
|
|
||||||
private static function log(int $postId, Throwable $e): void
|
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);
|
Log::failure(sprintf('lit/divi-replace-text on post %d', $postId), $e);
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function db(): \wpdb
|
private function db(): \wpdb
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi\Pages;
|
|||||||
use Closure;
|
use Closure;
|
||||||
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
||||||
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||||||
|
use LIT\MCP\Log;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,12 +44,7 @@ class DynamicContentMap
|
|||||||
try {
|
try {
|
||||||
$options = $this->registry !== null ? ($this->registry)($postId) : self::divi($postId);
|
$options = $this->registry !== null ? ($this->registry)($postId) : self::divi($postId);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$message = sprintf(
|
Log::failure(sprintf('divi dynamic content options of post %d', $postId), $e->getMessage());
|
||||||
'[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
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Media;
|
|||||||
use LIT\MCP\Abilities\Errors;
|
use LIT\MCP\Abilities\Errors;
|
||||||
use LIT\MCP\Abilities\WriteFailed;
|
use LIT\MCP\Abilities\WriteFailed;
|
||||||
use LIT\MCP\Limits;
|
use LIT\MCP\Limits;
|
||||||
|
use LIT\MCP\Log;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
|
||||||
@@ -197,8 +198,7 @@ class MediaImporter
|
|||||||
);
|
);
|
||||||
if ($attachmentId instanceof WP_Error) {
|
if ($attachmentId instanceof WP_Error) {
|
||||||
// WordPress' message can contain server paths: only for the log, not for the client.
|
// 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());
|
Log::failure('media sideload', $attachmentId->get_error_message());
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
|
|
||||||
return Errors::writeFailed(
|
return Errors::writeFailed(
|
||||||
'WordPress hat die Datei nicht übernommen (z. B. Upload-Verzeichnis nicht beschreibbar).'
|
'WordPress hat die Datei nicht übernommen (z. B. Upload-Verzeichnis nicht beschreibbar).'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\Integrations\Media;
|
namespace LIT\MCP\Integrations\Media;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use LIT\MCP\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds where an attachment is used (spec §6.3). Deliberately errs on the side of "used":
|
* 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;
|
$this->queryFailed = true;
|
||||||
$message = sprintf('[wp-lit-mcp] media usage query failed: %s', (string) $wpdb->last_error);
|
Log::failure('media usage query', (string) $wpdb->last_error);
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
+29
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Server;
|
|||||||
use LIT\MCP\Abilities\ExposurePolicy;
|
use LIT\MCP\Abilities\ExposurePolicy;
|
||||||
use LIT\MCP\Guides\InstructionsBuilder;
|
use LIT\MCP\Guides\InstructionsBuilder;
|
||||||
use LIT\MCP\Integrations\IntegrationRegistry;
|
use LIT\MCP\Integrations\IntegrationRegistry;
|
||||||
|
use LIT\MCP\Log;
|
||||||
use LIT\MCP\Plugin;
|
use LIT\MCP\Plugin;
|
||||||
use WP\MCP\Core\McpAdapter;
|
use WP\MCP\Core\McpAdapter;
|
||||||
use WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler;
|
use WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler;
|
||||||
@@ -131,8 +132,7 @@ final class ServerRegistrar
|
|||||||
$result = ($this->createAdapterServer)($args);
|
$result = ($this->createAdapterServer)($args);
|
||||||
|
|
||||||
if (is_wp_error($result)) {
|
if (is_wp_error($result)) {
|
||||||
$message = '[wp-lit-mcp] MCP server registration failed: ' . $result->get_error_message();
|
Log::failure('MCP server registration', $result->get_error_message());
|
||||||
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user