Del Error 500 al Control Total: Cómo creé mi propio monitor de WordPress en una tarde gracias a la IA
Introducción: el problema tras la actualización de WordPress
Todo comenzó como suele empezar cualquier lunes complicado para un webmaster: con una actualización mayor en WordPress. Esperas que todo vaya bien, cruzas los dedos, pero de repente, la pantalla se queda en blanco o aparece el temido mensaje:
500 Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.
Please contact the server administrator to inform them of the time this error occurred…
Este problema es muy común después de una actualización importante. El Error 500 es la forma que tiene el servidor de decirte: «Algo ha salido mal, pero no te diré exactamente qué». En mi caso, apareció justo después de un aviso que indicaba que era necesaria una actualización de la base de datos de WordPress.

Generalmente, esto ocurre por tres razones principales:
- Un plugin o tema incompatible con la nueva versión.
- Un archivo .htaccess corrupto.
- El problema silencioso: falta de memoria PHP en el servidor.
Para saber qué pasa realmente, no podemos ir a ciegas. Lo primero que hay que hacer es «encender la luz». Entré vía FTP, busqué el archivo wp-config.php y cambié esta línea:
define( 'WP_DEBUG', false );
Por esta:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
Esto genera un archivo debug.log dentro de la carpeta wp-content. Al abrirlo, pude ver los errores, desactivar los plugins conflictivos y borrar la caché. Sin embargo, me di cuenta de algo frustrante: el log nativo de WordPress es farragoso. Es un muro de texto interminable que te dice el error, pero no te dice cuánta memoria se estaba consumiendo en ese momento ni qué proceso (si era una visita del usuario, una tarea del administrador o un proceso automático) estaba saturando el servidor.
La necesidad de una herramienta propia
Necesitaba algo más que un simple registro de errores. Necesitaba un monitor de signos vitales para mi web.
Mi objetivo era claro: quería tener una visión precisa del consumo de memoria real, los tiempos de ejecución y la carga de la CPU en cada petición. Pero no quería instalar un plugin pesado de monitoreo (como Query Monitor o New Relic) que, irónicamente, consumiera más recursos de los que quería ahorrar.
Decidí desarrollar una herramienta ligera, «quirúrgica» y práctica. El problema es que programar esto desde cero me habría llevado días.
El papel de la inteligencia artificial
Aquí es donde entraron en juego las versiones gratuitas de Copilot y Gemini.
En lugar de escribir cada línea de código PHP, actué como el «arquitecto» y utilicé a la IA como mi «albañil senior». Mi enfoque fue utilizar la IA como un «programador en pareja» (pair programmer):
- Yo definía la lógica: «Necesito que filtres por tipo de petición», «quiero que el log se trunque para no llenar el disco», «ponme colores si pasa de 100MB».
- La IA escribía la sintaxis: Generaba el código base, las expresiones regulares y las funciones de WordPress.
- Yo depuraba: «Oye, esto da error si la variable no existe».
- La IA corregía: Refinando el código al instante.
Fue una experiencia reveladora: la IA no solo escribió código, sino que me ayudó a depurar errores y a optimizar la lógica de exportación CSV en cuestión de segundos. Lo que antes era un fin de semana de trabajo, se convirtió en una tarde creativa. En una sola tarde, pasé de tener un problema a tener una solución a medida.
Nace «Memory Logger»
El resultado de esta colaboración humano-IA es Memory Logger.
Es una herramienta de monitorización y diagnóstico diseñada específicamente para detectar cuellos de botella y optimizar los recursos del servidor. Su filosofía es simple: ser invisible hasta que la necesitas.
Características Clave:
- Registro Inteligente: No registra todo (eso saturaría el disco). Solo guarda datos si la petición supera un Umbral de Memoria que tú configuras (por ejemplo, 70 MB). Si la web va bien, el plugin calla. Si la web sufre, el plugin apunta.
- Clasificación de Solicitudes: Diferencia automáticamente si la carga viene del tráfico web (Frontend), de alguien trabajando en el panel (Backend) o de tareas programadas (Cron).
- Alertas Visibles: Olvídate de leer números. El sistema usa colores (Naranja para advertencia, Rojo para peligro) en las columnas de Memoria, Tiempo de Ejecución y CPU. Un vistazo rápido te dice dónde está el fuego.
Funcionamiento del plugin
El plugin opera de manera automática y silenciosa. Cada vez que alguien visita tu web o WordPress ejecuta una tarea, el plugin mide los signos vitales del servidor justo antes de terminar la ejecución.
Desde el panel de administración, Memory Logger ofrece:
- Panel de Control: Una tabla que muestra las últimas 200 entradas relevantes.
- Truncamiento Inteligente: Si el archivo de log crece demasiado (por defecto 2MB), el plugin borra automáticamente las entradas antiguas. Nunca llenará tu espacio en disco.
- Filtros Rápidos: Un desplegable para ver solo lo que te interesa: «¿Es el Cron el que me tira la web? ¿O es el tráfico del usuario?».
- Exportación CSV: Un botón para descargar los datos y analizarlos cómodamente en Excel o Google Sheets.
Cómo se instala (Instalación Rápida)
Para que esta herramienta sea realmente efectiva y pueda medir la memoria desde el inicio, se ha diseñado como un mu-plugin (Must-Use plugin).
- Accede a tu servidor vía FTP o Gestor de Archivos.
- Ve a la carpeta wp-content.
- Si no existe, crea una carpeta llamada mu-plugins.
- Sube el archivo memory-logger.php ahí dentro.
Ventaja: Al estar en mu-plugins, WordPress lo carga automáticamente antes que cualquier otro plugin normal. Esto significa que siempre estará activo, incluso si otros plugins fallan o si cambias de tema.
Guía de la Interfaz
Una vez subido, verás un nuevo menú en tu administrador llamado «Registro de Memoria».
- Configuración de Umbrales: Ajusta los valores según tu servidor. Si tienes un hosting modesto con 256MB de RAM, quizás quieras que te avise en rojo si una petición supera los 120MB.
- Acciones de Mantenimiento: Tienes botones para «Vaciar registro» (empezar de cero) y «Exportar registro» (para enviar el reporte a tu proveedor de hosting, por ejemplo).

Veamos que es cada columna
- Fecha: Momento exacto en que se registró la petición. Utilidad: Correlacionar picos de consumo con eventos específicos (campañas, publicaciones, ataques).
- Tipo: Tipo de solicitud procesada:
- Frontend: Páginas públicas visitadas por usuarios
- Backend: Acciones en el administrador de WordPress
- Cron: Tareas programadas automáticas (backups, actualizaciones, envío de emails)
- URL: Dirección específica que se cargó. Utilidad: Identificar qué página o proceso causa problemas de rendimiento.
- Pico Memoria (Real): Máximo consumo de RAM alcanzado durante la petición.
- < 100 MB = Normal/bajo
- 100-150 MB = Moderado (aceptable)
- 150-200 MB = Alto (⚠️ vigilar)
- > 200 MB = Muy alto (❌ problema crítico)
- Tiempo Real (s): Segundos que tardó WordPress en generar la respuesta.
- < 1s = Excelente ✅
- 1-3s = Aceptable
- 3-5s = Lento ⚠️
- > 5s = Muy lento ❌
- Límite WP: Límites de memoria configurados en WordPress:
- WP: Límite normal para frontend (ej: 256M)
- Max: Límite para backend/admin (ej: 768M o 2048M)
- Límite PHP: Límite de memoria del servidor PHP (ej: 768M). El valor
-1significa sin límite. Utilidad: Asegurar que PHP tiene suficiente memoria asignada. - Límite Tiempo: Tiempo máximo que PHP permite ejecutar un script antes de detenerlo:
- 120s: El script puede ejecutarse 2 minutos como máximo
- 0s: Sin límite (⚠️ peligroso, puede causar procesos infinitos)
- CPU (1m): Carga media de CPU en formato:
1min,5min,15min.- En un servidor con 8 CPU cores: 8.0 = 100% de uso
- < 7 = Normal
- 8.0 = Al límite (100%)
- > 8.0 = Sobrecargado (más del 100%)
8.70,7.46,7.23indica que el servidor está trabajando al 109% en el último minuto.
⚠️ Ejemplo de Caso Crítico Detectado
Línea 2 del registro – Tarea CRON problemática:
| Fecha | Tipo | URL | Memoria | Tiempo | CPU |
|---|---|---|---|---|---|
| 2025-12-04 21:30:11 | Cron | (no especificada) | 162 MB | 9.958 s | 8.70 |
🚨 Problemas identificados en este registro:
- Memoria excesiva: 162 MB para una tarea cron es muy alto. Las tareas automáticas deberían ser ligeras.
- Tiempo de ejecución crítico: 9.958 segundos (casi 10 segundos) indica un proceso mal optimizado o bloqueado.
- Sin límite de tiempo: Esta tarea tiene configurado
0sde límite, lo que significa que podría ejecutarse indefinidamente y bloquear el servidor. - CPU sobrecargada: Load average de 8.70 = servidor trabajando al 109% de capacidad.
- Sin URL identificativa: Al ser un Cron, no muestra qué tarea específica está causando el problema.
💡 Acciones recomendadas para este caso:
- Identificar la tarea Cron problemática: Instala un plugin como «WP Crontrol» para ver todas las tareas programadas y sus tiempos de ejecución.
- Desactivar temporalmente wp-cron.php: Añade
define('DISABLE_WP_CRON', true);enwp-config.phppara detener todas las tareas automáticas y verificar si el problema desaparece. - Revisar plugins de backup/sincronización: Los culpables habituales de crons pesados son:
- Plugins de backup automático
- Sincronizaciones con servicios externos
- Envío masivo de emails
- Optimizadores de base de datos
- Plugins de caché que regeneran contenido
- Configurar límites adecuados: Asegúrate de que las tareas cron tengan un límite de tiempo razonable (ej: 60-120 segundos) para evitar bloqueos.
- Optimizar o reprogramar: Si identificas la tarea, considera ejecutarla en horarios de menor tráfico o dividirla en lotes más pequeños.
⚡ Impacto en el rendimiento: Esta única tarea cron está consumiendo el equivalente de recursos de 20-30 páginas normales y está bloqueando el servidor durante 10 segundos. Durante ese tiempo, otras peticiones pueden quedar en cola o fallar.
¿Qué pasa si la web no carga?
Si el consumo de memoria es tan alto que tira el servidor, no podrás entrar a wp-admin para ver tu bonita tabla.
La buena noticia: Tu plugin ya está diseñado para esto. Como escribe los datos en un archivo físico en el servidor antes de mostrarlos en pantalla, el registro existe independientemente de si puedes ver el backend o no.
Cómo acceder a los datos «a la antigua»: Simplemente entra por FTP a tu carpeta /wp-content/ y encontrarás un archivo llamado memory-usage.log. Ahí verás la última línea registrada justo antes de que la web colapsara, lo cual te dirá qué URL o proceso fue el culpable.
Claro que siempre tienes los logs del servidor (PHP, Apache/Nginx) para complementar la información, pero la gracia de este plugin es que aporta un valor añadido: estructura los datos y los hace legibles, algo que los logs nativos no hacen bien.
Conclusión: lo que se puede lograr en una tarde con IA
La creación de Memory Logger me demostró que ya no es necesario ser un desarrollador experto para solucionar problemas técnicos complejos. Una tarea de depuración manual que me habría llevado días de frustración —activando y desactivando plugins, recargando páginas y leyendo textos planos— se resolvió creando una herramienta a medida en unas pocas horas.
La IA nos permite pasar de ser simples usuarios de software a creadores de nuestras propias soluciones. Obviamente, sé que muchos de vosotros estaréis pensando que ya existen herramientas gratuitas que hacen lo mismo, como Query Monitor, WP Server Health Stats o WP Crontrol, y también de pago como New Relic o ManageWP.
Sin embargo, Memory Logger está hecho a medida: se centra en memoria, CPU y tiempos de ejecución de una forma sencilla y directa. Y sinceramente, me lo tomé un poco como un reto personal y un entretenimiento útil.
Te invito a probar el código, instalarlo en tu mu-plugins y descubrir por fin qué es lo que realmente está consumiendo la memoria de tu WordPress. Ahora ya estoy pensando en nuevas implementaciones, como gráficos en tiempo real…
📥 Descarga el Código
<?php
/**
* Plugin Name: Memory Logger
* Plugin URI: https://www.posicionamientowebysem.com
* Description: Registra consumo de memoria y muestra el log en el backend con truncamiento inteligente, filtros, configuración de umbrales y exportación CSV.
* Version: 2.13
* Author: Josep Maria Tapia Estaragues
* Author URI: https://www.posicionamientowebysem.com
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*
* Uso:
* - Copiar este archivo en la carpeta `wp-content/mu-plugins/`.
* - El plugin registra el uso de memoria en cada carga de WordPress.
* - Los datos se guardan en `wp-content/memory-usage.log`.
* - Desde el backend se pueden visualizar, filtrar (Frontend, Backend, Cron),
* configurar umbrales y exportar el registro completo en CSV.
*/
// Tiempo de inicio
if ( !defined('WP_START_TIME') ) {
define('WP_START_TIME', microtime(true));
}
// Ruta del archivo de log
if ( !defined('MEMORY_LOG_FILE') ) {
define('MEMORY_LOG_FILE', WP_CONTENT_DIR . '/memory-usage.log');
}
// Opciones guardadas
function memory_logger_get_options() {
$defaults = [
'threshold_mb' => 70,
'max_size' => 2097152, // 2MB por defecto
'time_warn' => 2.0,
'time_risk' => 5.0,
'cpu_warn' => 1.0,
'cpu_risk' => 2.0,
];
// Se recomienda 'memory_logger_options' para evitar conflictos con otras opciones.
$opts = get_option('memory_logger_options', []);
return wp_parse_args($opts, $defaults);
}
// FUNCIONES AUXILIARES
/**
* Determina el tipo de solicitud para registrar (Frontend, Backend, Cron).
* @param string $url La URL de la solicitud.
* @return string El tipo de solicitud.
*/
function memory_logger_get_request_type($url) {
if (strpos($url, 'wp-cron.php') !== false || (defined('DOING_CRON') && DOING_CRON)) {
return 'Cron';
}
if (strpos($url, '/wp-admin') !== false || is_admin() || (defined('WP_ADMIN') && WP_ADMIN)) {
return 'Backend';
}
return 'Frontend';
}
// ====================================================================
// 1. REGISTRADOR (LOGGER)
// ====================================================================
add_action('shutdown', function() {
$opts = memory_logger_get_options();
// Función para convertir bytes a MB y redondear (Closure para compatibilidad con PHP < 7.4)
$to_mb = function($bytes) {
return round($bytes / 1024 / 1024, 2) . ' MB';
};
$mem_pic_real = memory_get_peak_usage(true);
// Solo loguear si supera el umbral
if ( $mem_pic_real < ($opts['threshold_mb'] * 1024 * 1024) ) return;
$mem_actual_real = $to_mb(memory_get_usage(true));
$mem_pic_real_mb = $to_mb($mem_pic_real);
// Obtener límites
$wp_limit = defined('WP_MEMORY_LIMIT') ? WP_MEMORY_LIMIT : 'no definido';
$wp_max = defined('WP_MAX_MEMORY_LIMIT') ? WP_MAX_MEMORY_LIMIT : 'no definido';
$php_limit = ini_get('memory_limit');
$php_time_limit = ini_get('max_execution_time');
$exec_time = round(microtime(true) - WP_START_TIME, 3);
global $wp_version;
$php_version = phpversion();
// Obtener carga de CPU
$cpu_load = function_exists('sys_getloadavg') ? implode(',', sys_getloadavg()) : 'no disponible';
$url = $_SERVER['REQUEST_URI'] ?? '(sin URL)';
$request_type = memory_logger_get_request_type($url);
// Formato interno: 13 campos
$msg = sprintf(
"[MEMORY-LOG] %s | TYPE: %s | URL: %s | Real: %s | Pic Real: %s | EXEC_TIME: %s | WP_LIMIT: %s | WP_MAX: %s | PHP_LIMIT: %s | PHP_TIME: %s | WP_VERSION: %s | PHP_VERSION: %s | CPU_LOAD: %s\n",
gmdate("Y-m-d H:i:s"),
$request_type,
$url,
$mem_actual_real,
$mem_pic_real_mb,
$exec_time,
$wp_limit,
$wp_max,
$php_limit,
$php_time_limit,
$wp_version ?? 'desconocida',
$php_version,
$cpu_load
);
// Truncamiento de log inteligente. Lee todo el contenido y conserva solo las últimas líneas.
if ( file_exists(MEMORY_LOG_FILE) && filesize(MEMORY_LOG_FILE) > $opts['max_size'] ) {
$content = @file_get_contents(MEMORY_LOG_FILE);
if ($content !== false) {
$lines = explode("\n", trim($content));
// Conservar las últimas 200 líneas (ajustable)
$last_lines = array_slice($lines, -200);
// Añadir un indicador de truncamiento
$truncate_indicator = "--- TRUNCADO (Tamaño Máximo: " . size_format($opts['max_size']) . ") ---\n";
@file_put_contents(MEMORY_LOG_FILE, $truncate_indicator . implode("\n", $last_lines) . "\n");
}
}
// Usamos FILE_APPEND para añadir la nueva línea al final
@file_put_contents(MEMORY_LOG_FILE, $msg, FILE_APPEND);
});
// ====================================================================
// 2. EXPORTACIÓN CSV
// ====================================================================
add_action('admin_post_memory_logger_export', 'memory_logger_export_csv');
function memory_logger_export_csv() {
// Seguridad: Verificar permisos y Nonce
if ( ! current_user_can('manage_options') || ! isset($_POST['export_nonce_field']) || ! wp_verify_nonce($_POST['export_nonce_field'], 'export_memory_log_action') ) {
wp_die('Acceso denegado.', '', ['response' => 403]);
}
$file = MEMORY_LOG_FILE;
if ( ! file_exists($file) ) {
wp_die('El archivo de registro no existe.');
}
$content = @file_get_contents($file);
// Generar Encabezado CSV
$csv_data = "Fecha,Tipo,URL,Memoria Actual (MB),Pico Real (MB),Tiempo Ejecución (s),Limite WP,Max WP,Limite PHP,Tiempo Limite PHP,Version WP,Version PHP,Carga CPU (1m)\n";
$lines = explode("\n", trim($content));
foreach ($lines as $line) {
$line = trim($line);
// Ignorar líneas vacías o el indicador de truncamiento
if (empty($line) || strpos($line, '[MEMORY-LOG]') !== 0) continue;
$raw_data = substr($line, strlen('[MEMORY-LOG] '));
$parts = explode(' | ', $raw_data);
if (count($parts) == 13) {
// Extracción de datos
$date = trim($parts[0]);
$type = trim(str_replace('TYPE: ', '', $parts[1]));
// Sanitización y escape robusto de la URL para CSV (Mejora de seguridad: reemplaza " por "")
$url_raw = trim(str_replace('URL: ', '', $parts[2]));
// str_replace('"', '""', ...) asegura que el campo se mantiene intacto si hay comillas internas.
$url = str_replace('"', '""', $url_raw);
$mem_actual = trim(str_replace(['Real: ', ' MB'], '', $parts[3]));
$mem_pic = trim(str_replace(['Pic Real: ', ' MB'], '', $parts[4]));
$exec_time = trim(str_replace(['EXEC_TIME: ', ' s'], '', $parts[5]));
$wp_limit = trim(str_replace('WP_LIMIT: ', '', $parts[6]));
$wp_max = trim(str_replace('WP_MAX: ', '', $parts[7]));
$php_limit = trim(str_replace('PHP_LIMIT: ', '', $parts[8]));
$php_time = trim(str_replace(['PHP_TIME: ', ' s'], '', $parts[9]));
$wp_version = trim(str_replace('WP_VERSION: ', '', $parts[10]));
$php_version = trim(str_replace('PHP_VERSION: ', '', $parts[11]));
$cpu_load = trim(str_replace('CPU_LOAD: ', '', $parts[12]));
// Construir línea CSV: todos los campos envueltos en comillas dobles para seguridad
$csv_data .= sprintf(
"\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
$date, $type, $url, $mem_actual, $mem_pic, $exec_time,
$wp_limit, $wp_max, $php_limit, $php_time, $wp_version, $php_version, $cpu_load
);
}
}
// Cabeceras de descarga
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="registro-memoria-' . date('Ymd_His') . '.csv"');
header('Pragma: no-cache');
header('Expires: 0');
// BOM para UTF-8 (necesario para la correcta visualización en Excel en español)
echo "\xEF\xBB\xBF";
echo $csv_data;
exit;
}
// ====================================================================
// 3. PÁGINA DE ADMINISTRACIÓN (Visualizador y Filtros)
// ====================================================================
add_action('admin_menu', function() {
add_menu_page(
'Registro de Memoria',
'Registro de Memoria',
'manage_options',
'memory-log-viewer',
'memory_logger_admin_page',
'dashicons-chart-area',
80
);
});
function memory_logger_admin_page() {
// Seguridad: Comprobación de capacidad de usuario
if ( ! current_user_can('manage_options') ) {
wp_die('No tienes suficientes permisos para acceder a esta página.');
}
$opts = memory_logger_get_options();
// 1. Guardar configuración
if ( isset($_POST['save_memory_logger']) && check_admin_referer('memory_logger_save_action') ) {
// Sanitización estricta al guardar (siempre usar floats/ints)
$opts['threshold_mb'] = floatval($_POST['threshold_mb']);
$opts['max_size'] = intval($_POST['max_size']);
$opts['time_warn'] = floatval($_POST['time_warn']);
$opts['time_risk'] = floatval($_POST['time_risk']);
$opts['cpu_warn'] = floatval($_POST['cpu_warn']);
$opts['cpu_risk'] = floatval($_POST['cpu_risk']);
// Validación de lógica de umbrales
if ($opts['time_risk'] <= $opts['time_warn']) $opts['time_risk'] = $opts['time_warn'] + 0.1;
if ($opts['cpu_risk'] <= $opts['cpu_warn']) $opts['cpu_risk'] = $opts['cpu_warn'] + 0.1;
update_option('memory_logger_options', $opts);
echo '<div class="updated notice is-dismissible"><p>Configuración guardada.</p></div>';
$opts = memory_logger_get_options();
}
// 2. Vaciar log
if ( isset($_POST['clear_memory_log']) && check_admin_referer('clear_memory_log_action') ) {
if ( file_exists(MEMORY_LOG_FILE) ) {
// Usamos file_put_contents con cadena vacía para vaciar el archivo
@file_put_contents(MEMORY_LOG_FILE, "");
echo '<div class="updated notice is-dismissible"><p>Registro vaciado correctamente.</p></div>';
} else {
echo '<div class="error notice is-dismissible"><p>El archivo de registro no existe.</p></div>';
}
}
if ( ! function_exists( 'get_plugin_data' ) ) {
// Cargar archivo necesario para get_plugin_data si no está disponible (ej. en WP-CLI)
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
$plugin_info = get_plugin_data( __FILE__ );
// Obtener versiones sistema de la última entrada del log (Optimizado: busca la última entrada válida)
$file = MEMORY_LOG_FILE;
$wp_version_summary = 'Desconocida';
$php_version_summary = 'Desconocida';
if ( file_exists($file) ) {
$content = @file_get_contents($file);
$lines = explode("\n", trim($content));
// Buscar desde el final para encontrar la información más reciente
for ($i = count($lines) - 1; $i >= 0; $i--) {
$line = trim($lines[$i]);
if (strpos($line, '[MEMORY-LOG]') === 0) {
$data_part = substr($line, strlen('[MEMORY-LOG] '));
$parts = explode(' | ', $data_part);
if (count($parts) >= 13) {
$wp_version_summary = trim(str_replace('WP_VERSION: ', '', $parts[10]));
$php_version_summary = trim(str_replace('PHP_VERSION: ', '', $parts[11]));
break;
}
}
}
}
// --- Output HTML ---
echo '<div class="wrap"><h1>Registro de Uso de Memoria</h1>';
// Bloque 1: Información del Plugin
echo '<h2>Información del Plugin</h2>';
echo '<div style="background:#fff; border:1px solid #c3c4c7; padding:15px; margin-bottom: 20px; border-radius: 4px;">';
echo '<p><strong>Nombre:</strong> ' . esc_html($plugin_info['Name']) . '</p>';
echo '<p><strong>Versión:</strong> ' . esc_html($plugin_info['Version']) . '</p>';
// Construcción del enlace del autor de forma segura
$author_name = wp_strip_all_tags($plugin_info['Author']);
$author_uri = esc_url($plugin_info['AuthorURI']);
$author_display = esc_html($author_name);
if (!empty($author_uri)) {
$author_display = sprintf(
'<a href="%s" target="_blank">%s</a>',
$author_uri,
esc_html($author_name)
);
}
echo '<p><strong>Autor:</strong> ' . $author_display . '</p>';
$description_clean = wp_strip_all_tags( $plugin_info['Description'] );
echo '<p><strong>Descripción:</strong> ' . esc_html($description_clean) . '</p>';
echo '</div>';
// Bloque 2: Versiones
echo '<h2>Versiones del Sistema</h2>';
echo '<div style="background:#fff; border:1px solid #c3c4c7; padding:15px; margin-bottom: 20px; border-radius: 4px;">
<p><strong>Versión de WordPress:</strong> ' . esc_html($wp_version_summary) . '</p>
<p><strong>Versión de PHP:</strong> ' . esc_html($php_version_summary) . '</p>
</div>';
// Bloque 3: Configuración
echo '<h2>Configuración de Umbrales</h2>';
echo '<form method="post">';
wp_nonce_field('memory_logger_save_action');
echo '<table class="form-table">
<tr>
<th scope="row"><label for="threshold_mb">Umbral de Memoria para Registrar (MB)</label></th>
<td><input type="number" id="threshold_mb" name="threshold_mb" value="' . esc_attr($opts['threshold_mb']) . '" step="1" required><p class="description">Solo se registra si el pico real supera este valor.</p></td>
</tr>
<tr>
<th scope="row"><label for="max_size">Tamaño máximo del registro (bytes)</label></th>
<td><input type="number" id="max_size" name="max_size" value="' . esc_attr($opts['max_size']) . '" step="1024" required><p class="description">Cuando se alcanza este tamaño, el registro se trunca a las últimas 200 líneas. ' . sprintf('(%s)', size_format($opts['max_size'])) . '</p></td>
</tr>
<tr>
<th scope="row"><label for="time_warn">Tiempo de Aviso (s)</label></th>
<td><input type="number" id="time_warn" name="time_warn" value="' . esc_attr($opts['time_warn']) . '" step="0.1" required><p class="description">Color naranja si el tiempo de ejecución es superior.</p></td>
</tr>
<tr>
<th scope="row"><label for="time_risk">Tiempo de Riesgo (s)</label></th>
<td><input type="number" id="time_risk" name="time_risk" value="' . esc_attr($opts['time_risk']) . '" step="0.1" required><p class="description">Color rojo si el tiempo de ejecución es superior (riesgo de timeout).</p></td>
</tr>
<tr>
<th scope="row"><label for="cpu_warn">CPU Aviso (1m load)</label></th>
<td><input type="number" id="cpu_warn" name="cpu_warn" value="' . esc_attr($opts['cpu_warn']) . '" step="0.1" required><p class="description">Color naranja si la carga media de 1 minuto supera este valor.</p></td>
</tr>
<tr>
<th scope="row"><label for="cpu_risk">CPU Riesgo (1m load)</label></th>
<td><input type="number" id="cpu_risk" name="cpu_risk" value="' . esc_attr($opts['cpu_risk']) . '" step="0.1" required><p class="description">Color rojo si la carga media de 1 minuto supera este valor.</p></td>
</tr>
</table>';
// Escapar el valor del botón
echo '<p><input type="submit" name="save_memory_logger" class="button button-primary" value="' . esc_attr('Guardar configuración') . '"></p>';
echo '</form>';
// Botones extra: Vaciado y Exportación (Ambos con Nonce y confirmación/acción admin-post)
echo '<form method="post" style="margin-top:15px;display:inline-block;">';
wp_nonce_field('clear_memory_log_action');
echo '<input type="submit" name="clear_memory_log" class="button button-secondary" value="' . esc_attr('Vaciar registro') . '" onclick="return confirm(\'' . esc_attr('¿Estás seguro de que quieres vaciar completamente el registro de memoria? Esta acción es irreversible.') . '\');">';
echo '</form>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin-top:15px;display:inline-block;margin-left:10px;">';
wp_nonce_field('export_memory_log_action', 'export_nonce_field');
echo '<input type="hidden" name="action" value="memory_logger_export" />';
echo '<input type="submit" name="export_memory_log_btn" class="button button-secondary" value="' . esc_attr('Exportar registro (.csv)') . '">';
echo '</form>';
// Filtros
$current_log_type = isset($_GET['log_type']) ? sanitize_text_field($_GET['log_type']) : 'all';
$types = ['all' => 'Todo', 'Frontend' => 'Frontend', 'Backend' => 'Backend', 'Cron' => 'Cron'];
$admin_url = admin_url('admin.php?page=memory-log-viewer');
echo '<div class="tablenav top" style="margin-top:15px;">';
echo '<div class="alignleft actions">';
echo '<form method="get" action="' . esc_url($admin_url) . '">';
echo '<input type="hidden" name="page" value="memory-log-viewer" />';
echo '<label for="log_type_filter" class="screen-reader-text">' . esc_html('Filtrar por tipo de solicitud') . '</label>';
echo '<select name="log_type" id="log_type_filter">';
foreach ($types as $value => $label) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr($value),
selected($current_log_type, $value, false),
esc_html($label)
);
}
echo '</select>';
echo '<input type="submit" class="button" value="' . esc_attr('Filtrar') . '">';
echo '</form>';
echo '</div>';
echo '<br class="clear"></div>';
// Tabla
echo '<h2>' . esc_html('Datos del Registro (Mostrando las últimas 200 entradas)') . '</h2>';
if ( file_exists($file) ) {
// Re-leer el contenido para la tabla (se podría optimizar, pero mantenemos la lógica de re-lectura por seguridad y simplicidad)
$content = @file_get_contents($file);
$lines = explode("\n", trim($content));
$last_lines = array_slice($lines, -200);
$filtered_lines_parts = [];
foreach ($last_lines as $line) {
$line = trim($line);
if (empty($line) || strpos($line, '[MEMORY-LOG]') !== 0) continue;
$data_part = substr($line, strlen('[MEMORY-LOG] '));
$parts = explode(' | ', $data_part);
if (count($parts) < 13) continue;
$request_type = trim(str_replace('TYPE: ', '', $parts[1]));
if ($current_log_type === 'all' || $request_type === $current_log_type) {
$filtered_lines_parts[] = $parts;
}
}
echo '<p style="font-size: 13px;">' . sprintf(esc_html('Mostrando %d entradas filtradas.'), count($filtered_lines_parts)) . '</p>';
echo '<table class="widefat fixed striped">';
echo '<thead><tr><th>' . esc_html('Fecha') . '</th><th>' . esc_html('Tipo') . '</th><th>' . esc_html('URL') . '</th><th>' . esc_html('Pico Memoria (Real)') . '</th><th>' . esc_html('Tiempo Real (s)') . '</th><th>' . esc_html('Límite WP') . '</th><th>' . esc_html('Límite PHP') . '</th><th>' . esc_html('Límite Tiempo') . '</th><th>' . esc_html('CPU (1m)') . '</th></tr></thead><tbody>';
foreach ($filtered_lines_parts as $parts) {
$date = esc_html($parts[0]);
$request_type = esc_html(trim(str_replace('TYPE: ', '', $parts[1])));
$raw_url = trim(str_replace('URL: ', '', $parts[2]));
// Crear el HTML para la URL (corta o completa con title)
$url_display = (strlen($raw_url) > 60)
? '<span title="' . esc_attr($raw_url) . '">' . esc_html(substr($raw_url, 0, 57)) . '...</span>'
: esc_html($raw_url);
$mem_pic_real_mb = esc_html(trim(str_replace('Pic Real: ', '', $parts[4])));
$exec_time = esc_html(trim(str_replace('EXEC_TIME: ', '', $parts[5])));
$wp_limit = esc_html(trim(str_replace('WP_LIMIT: ', '', $parts[6])));
$wp_max = esc_html(trim(str_replace('WP_MAX: ', '', $parts[7])));
$php_limit = esc_html(trim(str_replace('PHP_LIMIT: ', '', $parts[8])));
$php_time_limit = esc_html(trim(str_replace('PHP_TIME: ', '', $parts[9])));
$cpu_load_string = esc_html(trim(str_replace('CPU_LOAD: ', '', $parts[12])));
// Lógica de estilos para umbrales
$pic_val = (float)filter_var($mem_pic_real_mb, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$pic_style = '';
if ($pic_val > 120) $pic_style = 'color:red;font-weight:bold;';
elseif ($pic_val > 80) $pic_style = 'color:orange;font-weight:bold;';
$time_val = (float)filter_var($exec_time, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$time_style = '';
if ($time_val > $opts['time_risk']) $time_style = 'color:red;font-weight:bold;';
elseif ($time_val > $opts['time_warn']) $time_style = 'color:orange;font-weight:bold;';
$cpu_val_parts = explode(',', $cpu_load_string);
$cpu_val = isset($cpu_val_parts[0]) ? (float)filter_var($cpu_val_parts[0], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0;
$cpu_style = '';
if ($cpu_val > $opts['cpu_risk']) $cpu_style = 'color:red;font-weight:bold;';
elseif ($cpu_val > $opts['cpu_warn']) $cpu_style = 'color:orange;font-weight:bold;';
// Escapar los estilos inline
$pic_style_attr = esc_attr($pic_style);
$time_style_attr = esc_attr($time_style);
$cpu_style_attr = esc_attr($cpu_style);
echo "<tr>
<td>{$date}</td>
<td><strong>{$request_type}</strong></td>
<td>{$url_display}</td>
<td><span style='{$pic_style_attr}'>{$mem_pic_real_mb}</span></td>
<td><span style='{$time_style_attr}'>{$exec_time} s</span></td>
<td>WP: {$wp_limit} / Max: {$wp_max}</td>
<td>{$php_limit}</td>
<td>{$php_time_limit} s</td>
<td><span style='{$cpu_style_attr}'>{$cpu_load_string}</span></td>
</tr>";
}
echo '</tbody></table>';
} else {
echo '<p>' . sprintf(esc_html('No hay datos registrados aún. Asegúrese de que su consumo de memoria ha superado el umbral de: %s.'), '<strong>' . esc_html($opts['threshold_mb']) . ' MB</strong>') . '</p>';
}
echo '</div>';
}
?>



