false, 'level' => self::LEVEL_INFO, 'log_api_requests' => true, 'log_user_interactions' => false, // Privacy compliant 'max_log_days' => 30, 'max_file_size' => 10485760, // 10MB in bytes 'auto_cleanup' => true ); self::$settings = wp_parse_args( get_option('aipa_debug_settings', array()), $default_settings ); } return self::$settings; } /** * Update debug settings */ public static function update_settings($new_settings) { self::$settings = wp_parse_args($new_settings, self::get_settings()); update_option('aipa_debug_settings', self::$settings); return self::$settings; } /** * Get log directory */ public static function get_log_dir() { $upload_dir = wp_upload_dir(); $log_dir = $upload_dir['basedir'] . '/aipa-logs'; if (!file_exists($log_dir)) { wp_mkdir_p($log_dir); // Create .htaccess to protect log files $htaccess_content = "Order deny,allow\nDeny from all"; file_put_contents($log_dir . '/.htaccess', $htaccess_content); // Create index.php to prevent directory listing file_put_contents($log_dir . '/index.php', ' $settings['level']) { return false; } $log_entry = self::format_log_entry($level, $context, $message, $data); return self::write_to_file($log_entry); } /** * Convenience methods for different log levels */ public static function error($context, $message, $data = null) { return self::log(self::LEVEL_ERROR, $context, $message, $data); } public static function warning($context, $message, $data = null) { return self::log(self::LEVEL_WARNING, $context, $message, $data); } public static function info($context, $message, $data = null) { return self::log(self::LEVEL_INFO, $context, $message, $data); } public static function debug($context, $message, $data = null) { return self::log(self::LEVEL_DEBUG, $context, $message, $data); } /** * Log slow operation with automatic timing * * @param string $context Context name * @param string $operation Operation description * @param float $start_time Start time from microtime(true) * @param array $data Additional data * @param float $threshold Time threshold in seconds (default 1.0) */ public static function log_slow_operation($context, $operation, $start_time, $data = array(), $threshold = 1.0) { $elapsed = microtime(true) - $start_time; if ($elapsed >= $threshold) { $data['execution_time'] = $elapsed; $data['memory_usage'] = memory_get_usage(true) / 1048576 . ' MB'; $data['memory_peak'] = memory_get_peak_usage(true) / 1048576 . ' MB'; self::log(self::LEVEL_WARNING, $context, 'Slow operation detected: ' . $operation, $data); } } /** * Format log entry */ private static function format_log_entry($level, $context, $message, $data = null) { $level_names = array( self::LEVEL_ERROR => 'ERROR', self::LEVEL_WARNING => 'WARNING', self::LEVEL_INFO => 'INFO', self::LEVEL_DEBUG => 'DEBUG' ); $timestamp = current_time('Y-m-d H:i:s'); $level_name = isset($level_names[$level]) ? $level_names[$level] : 'UNKNOWN'; $entry = "[{$timestamp}] [{$level_name}] [{$context}] {$message}"; if ($data !== null) { $entry .= " | Data: " . self::sanitize_data_for_log($data); } $entry .= PHP_EOL; return $entry; } /** * Sanitize sensitive data for logging (WordPress compliant) */ private static function sanitize_data_for_log($data) { if (is_string($data)) { // Don't log full prompts or responses to protect privacy if (strlen($data) > 200) { return substr($data, 0, 200) . '... [truncated for privacy]'; } return $data; } if (is_array($data) || is_object($data)) { $data = (array) $data; // Remove sensitive keys $sensitive_keys = array( 'api_key', 'password', 'secret', 'token', 'user_pass', 'user_email', // WordPress guideline 'display_name', // WordPress guideline 'user_login', // WordPress guideline 'request_prompt', // Full user queries 'response_text' // Full AI responses ); foreach ($sensitive_keys as $key) { if (isset($data[$key])) { if (in_array($key, array('api_key', 'password', 'secret', 'token'))) { // Show only first 4 and last 4 characters for API keys $value = $data[$key]; if (strlen($value) > 8) { $data[$key] = substr($value, 0, 4) . '****' . substr($value, -4); } else { $data[$key] = '****'; } } else { $data[$key] = '[REDACTED]'; } } } return json_encode($data, JSON_UNESCAPED_UNICODE); } // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export -- Debug logging requires var_export return var_export($data, true); } /** * Write log entry to file */ private static function write_to_file($log_entry) { $log_file = self::get_current_log_file(); // Check file size limit $settings = self::get_settings(); if (file_exists($log_file) && filesize($log_file) > $settings['max_file_size']) { // Create new file with timestamp $log_dir = self::get_log_dir(); $timestamp = current_time('Y-m-d_H-i-s'); $log_file = $log_dir . '/aipa-debug-' . $timestamp . '.log'; } $result = file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX); // Auto cleanup old logs if ($settings['auto_cleanup']) { self::cleanup_old_logs(); } return $result !== false; } /** * Get all log files */ public static function get_log_files() { $log_dir = self::get_log_dir(); $files = glob($log_dir . '/aipa-debug-*.log'); if (!$files) { return array(); } // Sort by modification time (newest first) usort($files, function($a, $b) { return filemtime($b) - filemtime($a); }); $log_files = array(); foreach ($files as $file) { $log_files[] = array( 'filename' => basename($file), 'filepath' => $file, 'size' => filesize($file), 'modified' => filemtime($file), 'readable_size' => self::human_filesize(filesize($file)), 'readable_date' => gmdate('Y-m-d H:i:s', filemtime($file)) ); } return $log_files; } /** * Read log file content */ public static function read_log_file($filename, $tail_lines = 100) { $log_dir = self::get_log_dir(); $file_path = $log_dir . '/' . basename($filename); if (!file_exists($file_path)) { return false; } if ($tail_lines > 0) { return self::tail_file($file_path, $tail_lines); } else { return file_get_contents($file_path); } } /** * Get last N lines from file (like tail command) */ private static function tail_file($file_path, $lines = 100) { global $wp_filesystem; // Initialize WordPress filesystem if not already done if (!function_exists('WP_Filesystem')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if (!WP_Filesystem()) { // Fallback to direct file operations if WP_Filesystem fails if (!file_exists($file_path)) { return false; } $content = file_get_contents($file_path); } else { $content = $wp_filesystem->get_contents($file_path); } if ($content === false) { return false; } // Split into lines and get last N lines $all_lines = explode("\n", $content); $total_lines = count($all_lines); if ($total_lines <= $lines) { return $content; } $last_lines = array_slice($all_lines, -$lines); return implode("\n", $last_lines); } /** * Delete log file */ public static function delete_log_file($filename) { $log_dir = self::get_log_dir(); $file_path = $log_dir . '/' . basename($filename); if (file_exists($file_path)) { return wp_delete_file($file_path); } return false; } /** * Cleanup old log files */ public static function cleanup_old_logs() { $settings = self::get_settings(); $max_days = $settings['max_log_days']; $cutoff_time = time() - ($max_days * 24 * 60 * 60); $log_files = self::get_log_files(); foreach ($log_files as $file_info) { if ($file_info['modified'] < $cutoff_time) { wp_delete_file($file_info['filepath']); } } } /** * Convert bytes to human readable format */ private static function human_filesize($bytes, $decimals = 2) { $size = array('B', 'KB', 'MB', 'GB', 'TB'); $factor = floor((strlen($bytes) - 1) / 3); return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor]; } /** * Clear all logs */ public static function clear_all_logs() { $log_files = self::get_log_files(); $deleted = 0; foreach ($log_files as $file_info) { if (wp_delete_file($file_info['filepath'])) { $deleted++; } } return $deleted; } }