PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | includes/class-metasync-error-logger.php +50 -34 2.6.22 → 2.7.1 View file →
@@ -39,8 +39,9 @@
39 39 const CATEGORY_AUTHENTICATION_FAILURE = 'AUTHENTICATION_FAILURE';
40 40 const CATEGORY_DATABASE_ERROR = 'DATABASE_ERROR';
41 41 const CATEGORY_NETWORK_ERROR = 'NETWORK_ERROR';
42 42 const CATEGORY_OTTO_RENDER = 'OTTO_RENDER';
43 + const CATEGORY_HEADLESS_DELIVERY = 'HEADLESS_DELIVERY';
43 44
44 45 /**
45 46 * Error codes mapping
46 47 * Format: MS-XXXX
@@ -54,8 +55,9 @@
54 55 self::CATEGORY_AUTHENTICATION_FAILURE => 'MS-4001',
55 56 self::CATEGORY_DATABASE_ERROR => 'MS-5001',
56 57 self::CATEGORY_NETWORK_ERROR => 'MS-6001',
57 58 self::CATEGORY_OTTO_RENDER => 'MS-7001',
59 + self::CATEGORY_HEADLESS_DELIVERY => 'MS-8001',
58 60 ];
59 61
60 62 /**
61 63 * Categories that only exist while an operator is troubleshooting.
@@ -105,8 +107,14 @@
105 107 */
106 108 const LOG_FILE_NAME = 'metasync-errors.log';
107 109
108 110 /**
111 + * Maximum log file size before rotation (10MB), mirroring the debug-mode
112 + * log policy: current file plus a single .old rotation.
113 + */
114 + const MAX_LOG_SIZE = 10485760; // 10MB in bytes
115 +
116 + /**
109 117 * Main logging function
110 118 *
111 119 * Formats and writes structured error log with:
112 120 * - Timestamp
@@ -142,13 +150,16 @@
142 150 $error_code = self::$error_codes[$category] ?? 'MS-0000';
143 151
144 152 // Prepare full context with error code
145 153 $full_context = array_merge([
146 - 'error_code' => $error_code
154 + 'error_code' => $error_code,
155 + 'severity' => $severity
147 156 ], $context);
148 157
149 - // Format timestamp
150 - $timestamp = date('Y-m-d H:i:s');
158 + // Format timestamp — current_time() keeps the log line on the same
159 + // WordPress-local clock as the error summary below, instead of the
160 + // server/UTC clock date() uses.
161 + $timestamp = current_time('Y-m-d H:i:s');
151 162
152 163 // Format log line: [YYYY-MM-DD HH:MM:SS] [CATEGORY] [SEVERITY] Message {context_json}
153 164 $log_line = sprintf(
154 165 "[%s] [%s] [%s] %s %s\n",
@@ -177,52 +188,57 @@
177 188 * @param string $log_line Formatted log line
178 189 * @return bool True on success, false on failure
179 190 */
180 191 private static function write_to_log_file($log_line) {
181 - // Get log directory (same as Log_Manager uses)
182 - $log_directory = WP_CONTENT_DIR . '/metasync_data';
192 + // Directory creation, web-server protection, and writability
193 + // guarantees live in Metasync_Data_Store so every writer agrees on
194 + // the location; a false return means "no writable data directory".
195 + $log_directory = Metasync_Data_Store::base_dir();
196 + if (false === $log_directory) {
197 + return false;
198 + }
199 +
200 + // Get log file path
201 + $log_file = Metasync_Data_Store::file_path(self::LOG_FILE_NAME);
202 + if (false === $log_file) {
203 + return false;
204 + }
183 205
184 - // Create directory if it doesn't exist
185 - if (!is_dir($log_directory)) {
186 - if (!@mkdir($log_directory, 0755, true)) {
187 - error_log('Metasync_Error_Logger: Failed to create log directory: ' . $log_directory);
188 - return false;
206 + // Serialize rotation and append across requests. Locking only the log
207 + // file is insufficient because rename() changes the inode being locked.
208 + $rotation_lock = @fopen($log_file . '.lock', 'c'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming file I/O; WP_Filesystem cannot return a raw stream handle
209 + if (false === $rotation_lock || !flock($rotation_lock, LOCK_EX)) {
210 + if (false !== $rotation_lock) {
211 + fclose($rotation_lock); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle
189 212 }
213 + error_log('Metasync_Error_Logger: Failed to lock log file: ' . $log_file); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- plugin logger sink / last-resort fallback
214 + return false;
190 215 }
191 216
192 - // Protect log directory from direct web access
193 - $htaccess_file = $log_directory . '/.htaccess';
194 - if (!file_exists($htaccess_file)) {
195 - @file_put_contents($htaccess_file, "Order deny,allow\nDeny from all\n");
196 - }
197 - $index_file = $log_directory . '/index.php';
198 - if (!file_exists($index_file)) {
199 - @file_put_contents($index_file, "<?php\n// Silence is golden\n");
200 - }
201 -
202 - // Verify directory is writable
203 - if (!is_writable($log_directory)) {
204 - @chmod($log_directory, 0755);
205 - if (!is_writable($log_directory)) {
206 - error_log('Metasync_Error_Logger: Directory not writable: ' . $log_directory);
207 - return false;
217 + // Rotate while holding the stable sidecar lock so concurrent requests
218 + // cannot both unlink/rename the same file or append to the old inode.
219 + $log_size = file_exists($log_file) ? filesize($log_file) : false;
220 + if (false !== $log_size && $log_size >= self::MAX_LOG_SIZE) {
221 + $backup = $log_file . '.old';
222 + if (file_exists($backup)) {
223 + @wp_delete_file($backup);
208 224 }
225 + @rename($log_file, $backup); // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- atomic same-filesystem replace; WP_Filesystem::move() needs credential bootstrapping on non-direct hosts
209 226 }
210 -
211 - // Get log file path
212 - $log_file = wp_normalize_path($log_directory . '/' . self::LOG_FILE_NAME);
213 -
227 +
214 228 // Append to log file with file locking
215 229 $result = @file_put_contents($log_file, $log_line, FILE_APPEND | LOCK_EX);
216 -
230 + flock($rotation_lock, LOCK_UN);
231 + fclose($rotation_lock); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle
232 +
217 233 if ($result === false) {
218 - error_log('Metasync_Error_Logger: Failed to write to log file: ' . $log_file);
234 + error_log('Metasync_Error_Logger: Failed to write to log file: ' . $log_file); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- plugin logger sink / last-resort fallback
219 235 return false;
220 236 }
221 237
222 238 // Set file permissions if file was just created
223 239 if (!file_exists($log_file) || filesize($log_file) === strlen($log_line)) {
224 - @chmod($log_file, 0644);
240 + @chmod($log_file, 0644); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- direct POSIX permission change; WP_Filesystem would prompt for FTP credentials on non-direct hosts
225 241 }
226 242
227 243 return true;
228 244 }
@@ -441,8 +457,8 @@
441 457
442 458 // Also check on shutdown (for immediate detection, throttled)
443 459 add_action('shutdown', function() {
444 460 // Only check in admin or if triggered manually
445 - if (is_admin() || isset($_GET['metasync_check_queue'])) {
461 + if (is_admin() || isset($_GET['metasync_check_queue'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only, throttled queue check
446 462 metasync_check_action_scheduler_queue_overflow();
447 463 }
448 464 }, 999);