PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / trunk
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). vtrunk
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / vendor / wordpress / mcp-adapter / includes / Infrastructure / Observability / McpObservabilityHelperTrait.php
commercebird / vendor / wordpress / mcp-adapter / includes / Infrastructure / Observability Last commit date
Contracts 2 months ago ConsoleObservabilityHandler.php 2 months ago ErrorLogMcpObservabilityHandler.php 2 months ago FailureReason.php 2 months ago McpObservabilityHelperTrait.php 2 months ago NullMcpObservabilityHandler.php 2 months ago
McpObservabilityHelperTrait.php
220 lines
1 <?php
2 /**
3 * Helper trait for MCP observability handlers providing shared utility methods.
4 *
5 * @package McpAdapter
6 */
7
8 declare( strict_types=1 );
9
10 namespace WP\MCP\Infrastructure\Observability;
11
12 /**
13 * Trait McpObservabilityHelperTrait
14 *
15 * Provides shared utility methods for observability handlers including
16 * tag management, metric formatting, and sanitization functionality.
17 */
18 trait McpObservabilityHelperTrait {
19 /**
20 * Error categories keyed by throwable class name.
21 *
22 * @used-by ::categorize_error() method.
23 */
24 private static array $error_categories = array(
25 \ArgumentCountError::class => 'arguments',
26 \Error::class => 'system',
27 \InvalidArgumentException::class => 'validation',
28 \LogicException::class => 'logic',
29 \RuntimeException::class => 'execution',
30 \TypeError::class => 'type',
31 );
32
33 /**
34 * Patterns that indicate sensitive data in keys or values.
35 *
36 * These patterns are designed to match both camelCase and snake_case variants:
37 * - apiKey, api_key, API_KEY
38 * - authToken, auth_token, AUTH_TOKEN
39 * - secretKey, secret_key, SECRET_KEY
40 *
41 * @var string[]
42 */
43 private static array $sensitive_patterns = array(
44 'password',
45 'passwd',
46 'pwd',
47 'secret',
48 'token',
49 'bearer',
50 'credential',
51 'private',
52 'apikey',
53 'api_key',
54 'authtoken',
55 'auth_token',
56 'accesstoken',
57 'access_token',
58 'refreshtoken',
59 'refresh_token',
60 'clientsecret',
61 'client_secret',
62 'privatekey',
63 'private_key',
64 'secretkey',
65 'secret_key',
66 'authorization',
67 'authenticate',
68 'encryption',
69 );
70
71 /**
72 * Format metric name to follow consistent naming conventions.
73 *
74 * @param string $metric The raw metric name.
75 *
76 * @return string
77 */
78 public static function format_metric_name( string $metric ): string {
79 // Ensure metric starts with 'mcp.' prefix.
80 if ( ! str_starts_with( $metric, 'mcp.' ) ) {
81 $metric = 'mcp.' . $metric;
82 }
83
84 // Convert to lowercase and replace spaces/special chars with dots.
85 $metric = strtolower( $metric );
86 $metric = (string) preg_replace( '/[^a-z0-9_\.]/', '.', $metric );
87 $metric = (string) preg_replace( '/\.+/', '.', $metric ); // Remove duplicate dots.
88 // Remove leading/trailing dots.
89
90 return trim( $metric, '.' );
91 }
92
93 /**
94 * Merge default tags with provided tags.
95 *
96 * @param array $tags The user-provided tags.
97 *
98 * @return array
99 */
100 public static function merge_tags( array $tags ): array {
101 $default_tags = self::get_default_tags();
102 $merged_tags = array_merge( $default_tags, $tags );
103
104 return self::sanitize_tags( $merged_tags );
105 }
106
107 /**
108 * Get default tags that should be included with all metrics.
109 *
110 * @return array
111 */
112 public static function get_default_tags(): array {
113 return array(
114 'site_id' => function_exists( 'get_current_blog_id' ) ? get_current_blog_id() : 0,
115 'user_id' => function_exists( 'get_current_user_id' ) ? get_current_user_id() : 0,
116 'timestamp' => time(),
117 );
118 }
119
120 /**
121 * Sanitize tags to ensure they are safe for logging and don't contain sensitive data.
122 *
123 * @param array $tags The tags to sanitize.
124 *
125 * @return array
126 */
127 public static function sanitize_tags( array $tags ): array {
128 $sanitized = array();
129
130 foreach ( $tags as $key => $value ) {
131 // Convert key to string and limit length to prevent log bloat.
132 $key = substr( (string) $key, 0, 64 );
133
134 // Check if the key itself indicates sensitive data.
135 if ( self::is_sensitive_key( $key ) ) {
136 $sanitized[ $key ] = '[REDACTED]';
137 continue;
138 }
139
140 // Convert value to string, handling null specially.
141 if ( null === $value ) {
142 $value = '';
143 } elseif ( is_scalar( $value ) ) {
144 $value = (string) $value;
145 } else {
146 $value = wp_json_encode( $value );
147 // wp_json_encode can return false on failure, ensure we have a string.
148 if ( false === $value ) {
149 $value = '';
150 }
151 }
152
153 // Limit value length to prevent log bloat.
154 if ( strlen( $value ) > 1024 ) {
155 $value = substr( $value, 0, 1024 ) . '...[truncated]';
156 }
157
158 // Remove potentially sensitive information patterns from values.
159 $value = self::redact_sensitive_values( $value );
160
161 $sanitized[ $key ] = $value;
162 }
163
164 return $sanitized;
165 }
166
167 /**
168 * Check if a key name indicates sensitive data.
169 *
170 * Matches patterns in camelCase, snake_case, and SCREAMING_CASE.
171 *
172 * @param string $key The key name to check.
173 *
174 * @return bool True if the key appears to contain sensitive data.
175 */
176 public static function is_sensitive_key( string $key ): bool {
177 // Normalize: lowercase and remove underscores/hyphens for pattern matching.
178 $normalized = strtolower( str_replace( array( '_', '-' ), '', $key ) );
179
180 foreach ( self::$sensitive_patterns as $pattern ) {
181 // Remove underscores from pattern for normalized comparison.
182 $normalized_pattern = str_replace( '_', '', $pattern );
183
184 if ( str_contains( $normalized, $normalized_pattern ) ) {
185 return true;
186 }
187 }
188
189 return false;
190 }
191
192 /**
193 * Redact sensitive values from a string.
194 *
195 * Uses a more comprehensive pattern that catches compound words.
196 *
197 * @param string $value The value to redact.
198 *
199 * @return string The value with sensitive patterns redacted.
200 */
201 public static function redact_sensitive_values( string $value ): string {
202 // Build a regex pattern that matches sensitive words as substrings.
203 // This catches camelCase (apiKey), snake_case (api_key), and standalone words.
204 $pattern = '/(?:' . implode( '|', array_map( 'preg_quote', self::$sensitive_patterns ) ) . ')/i';
205
206 return (string) preg_replace( $pattern, '[REDACTED]', $value );
207 }
208
209 /**
210 * Categorize an exception into a general error category.
211 *
212 * @param \Throwable $exception The exception to categorize.
213 *
214 * @return string
215 */
216 public static function categorize_error( \Throwable $exception ): string {
217 return self::$error_categories[ get_class( $exception ) ] ?? 'unknown';
218 }
219 }
220