- VERSION 5.09.16

- feat: Implementa control de logs de SQLite granular por DBKey y corrige la concurrencia del Timer en Hot-Swap.
- Este commit introduce una mejora crucial en el rendimiento y la flexibilidad del servidor al permitir el control detallado del registro de logs en SQLite (users.db) por cada base de datos configurada (DB1, DB2, etc.).
- Cambios Principales y Beneficios:
1. Control Granular de Logs: Se reemplazó el flag de control global de logs por un mapa (SQLiteLoggingStatusByDB), permitiendo al administrador deshabilitar el costoso proceso de escritura de query_logs y errores para bases de datos específicas mediante la propiedad enableSQLiteLogs en sus archivos .properties correspondientes.
2. Estabilización del Timer y Hot-Swap:
    ◦ Se corrigió un problema de concurrencia y estado asegurando que timerLogs se inicialice incondicionalmente, resolviendo el error IllegalStateException: Interval must be larger than 0 que ocurría durante el reload.
    ◦ El Timer de limpieza (borraArribaDe15000Logs y VACUUM) ahora se activa solo si al menos una base de datos tiene el logging habilitado (IsAnySQLiteLoggingEnabled), minimizando el overhead de E/S de disco cuando los logs no se requieren.
3. Recarga Dinámica de Estado: El comando manager?command=reload ahora lee la configuración enableSQLiteLogs de todos los conectores nuevos y actualiza atómicamente el estado global de logs, aplicando los cambios sin requerir un reinicio del servidor.
This commit is contained in:
2025-09-19 12:31:03 -06:00
parent 3b352bb105
commit dbfafbf9ac
8 changed files with 347 additions and 242 deletions

View File

@@ -49,7 +49,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
' toolkit.InitializeStatic("java.awt.Toolkit")
' Dim screenRect As JavaObject
' screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
' toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
' toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
' Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect))
' Dim ImageIO As JavaObject
' ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream))
@@ -110,119 +110,158 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
' =========================================================================
If Command = "reload" Then
' Usamos un StringBuilder temporal para acumular los logs de la recarga antes de añadirlos al StringBuilder principal.
Dim sbTemp As StringBuilder
sbTemp.Initialize
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append("<br>" & CRLF)
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append(" " & CRLF)
' <<< PASO CLAVE 1: DETENER TIMER DE LOGS (ZONA SEGURA DE SQLite) >>>
' Detenemos el timer incondicionalmente al inicio para evitar conflictos de bloqueo con SQLite
' durante la inicialización de conectores.
Dim oldTimerState As Boolean = Main.timerLogs.Enabled
If oldTimerState Then
Main.timerLogs.Enabled = False
sbTemp.Append(" -> Timer de limpieza de logs (SQLite) detenido temporalmente.").Append(" " & CRLF)
End If
' 1. Crear un nuevo mapa temporal para almacenar los conectores recién inicializados.
Dim newConnectors As Map
newConnectors.Initialize
' Guardamos una referencia al mapa de conectores actualmente activos.
Dim oldConnectors As Map
Dim reloadSuccessful As Boolean = True
' *** INICIO DEL BLOQUE CRÍTICO 1: Obtener oldConnectors con ReentrantLock ***
Dim lock1Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
Dim lock1Acquired As Boolean = False
Try
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
lock1Acquired = True ' Marcamos que el bloqueo fue adquirido.
oldConnectors = Main.Connectors ' Obtenemos la referencia al mapa actual de conectores.
Main.MainConnectorsLock.RunMethod("lock", Null)
lock1Acquired = True
oldConnectors = Main.Connectors
Catch
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append("<br>" & CRLF)
reloadSuccessful = False ' Si falla aquí, la recarga no puede continuar.
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append(" " & CRLF)
reloadSuccessful = False
End Try
If lock1Acquired Then
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
End If
' *** FIN DEL BLOQUE CRÍTICO 1 ***
If Not(reloadSuccessful) Then ' Si el primer bloqueo falló o la asignación, salimos temprano.
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados al StringBuilder principal.
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
Return ' Salir del Handle ya que ocurrió un error crítico irrecuperable.
If lock1Acquired Then
Main.MainConnectorsLock.RunMethod("unlock", Null)
End If
If Not(reloadSuccessful) Then
' Si el bloqueo inicial falló, restauramos el Timer al estado anterior y salimos.
If oldTimerState Then Main.timerLogs.Enabled = True
sb.Append(sbTemp.ToString)
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
Return
End If
' 2. Iterar sobre las bases de datos configuradas y crear *nuevas* instancias de RDCConnector.
For Each dbKey As String In Main.listaDeCP
Try
Dim newRDC As RDCConnector
newRDC.Initialize(dbKey) ' Inicializa la nueva instancia con la configuración fresca.
' <<< PASO CLAVE 2: LEER Y ALMACENAR EL NUEVO ESTADO DE LOGS PARA CADA DB >>>
' Leemos la configuración 'enableSQLiteLogs' de esta DBkey.
Dim enableLogsSetting As Int = newRDC.config.GetDefault("enableSQLiteLogs", 0).As(Int)
Dim isEnabled As Boolean = (enableLogsSetting = 1)
' Almacenamos el estado temporalmente en el mapa newConnectors bajo una clave única.
newConnectors.Put(dbKey & "_LOG_STATE", isEnabled)
sbTemp.Append($" -> Logs de ${dbKey} activados: ${isEnabled}"$).Append(" " & CRLF)
' <<< FIN PASO CLAVE 2 >>>
newConnectors.Put(dbKey, newRDC)
Dim newPoolStats As Map = newRDC.GetPoolStats
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append("<br>" & CRLF)
Catch
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("<br>" & CRLF)
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append(" " & CRLF)
Catch
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append(" " & CRLF)
reloadSuccessful = False
Exit ' Si uno falla, abortamos la recarga completa para evitar un estado inconsistente.
Exit ' Si uno falla, abortamos la recarga.
End Try
Next
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados de la inicialización al StringBuilder principal.
sb.Append(sbTemp.ToString)
If reloadSuccessful Then
' 3. Si todos los nuevos conectores se inicializaron con éxito,
' realizamos el "cambio de cartel" (hot-swap) de forma atómica.
' 3. Si todo fue exitoso, hacemos el Hot-Swap atómico.
' *** INICIO DEL BLOQUE CRÍTICO 2: Reemplazar Main.Connectors con ReentrantLock ***
Dim lock2Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
Dim lock2Acquired As Boolean = False
Try
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
lock2Acquired = True ' Marcamos que el bloqueo fue adquirido.
Main.MainConnectorsLock.RunMethod("lock", Null)
lock2Acquired = True
Main.Connectors = newConnectors ' Reemplazamos el mapa de conectores completo por el nuevo.
Catch
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append("<br>" & CRLF)
reloadSuccessful = False ' Si falla aquí, la recarga no se completó con éxito.
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append(" " & CRLF)
reloadSuccessful = False
End Try
If lock2Acquired Then
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
End If
' *** FIN DEL BLOQUE CRÍTICO 2 ***
If reloadSuccessful Then ' Si el segundo bloqueo y swap fue exitoso
sb.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append("<br>" & CRLF)
sb.Append($"Nuevos conectores activos. Verificando estado final..."$).Append("<br>" & CRLF)
' Mostrar el estado de los *nuevos* conectores después del swap.
If Main.Connectors.IsInitialized Then
Dim liveStats As Map
liveStats.Initialize
For Each dbKey As String In Main.Connectors.Keys
Dim currentConnector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
liveStats.Put(dbKey, currentConnector.GetPoolStats) ' Obtiene las estadísticas en tiempo real.
Next
j.Initialize(liveStats)
sb.Append($"Estado actual de los pools: ${j.ToString}"$).Append(CRLF) ' No <br> para JSON puro
End If
' 4. Cerrar explícitamente los pools de conexión de las instancias antiguas.
If oldConnectors.IsInitialized Then
sb.Append("Cerrando conectores antiguos...").Append("<br>" & CRLF)
For Each dbKey As String In oldConnectors.Keys
Dim oldRDC As RDCConnector = oldConnectors.Get(dbKey).As(RDCConnector)
If oldRDC <> Null And oldRDC.IsInitialized Then
oldRDC.Close ' Llama al método Close que hemos añadido al RDCConnector.
sb.Append($" -> Pool antiguo de ${dbKey} cerrado."$).Append("<br>" & CRLF)
End If
Next
End If
Else
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de reemplazo de conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
If lock2Acquired Then
Main.MainConnectorsLock.RunMethod("unlock", Null)
End If
Else
sb.Append($"¡ERROR: La recarga de configuración falló durante la inicialización de nuevos conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
' Si la recarga falló, los conectores antiguos (oldConnectors) se mantienen activos
' y siguen sirviendo para evitar un paro del servicio.
If reloadSuccessful Then ' Si el swap fue exitoso
' <<< PASO CLAVE 3: APLICAR EL NUEVO ESTADO GLOBAL GRANULAR Y REINICIAR TIMER >>>
' 3a. Reemplazar el mapa de estados de logging granular
Main.SQLiteLoggingStatusByDB.Clear ' Limpiamos el mapa global
Dim isAnyEnabled As Boolean = False
For Each dbKey As String In Main.listaDeCP
' Recuperamos el estado logueado temporalmente.
Dim isEnabled As Boolean = newConnectors.Get(dbKey & "_LOG_STATE").As(Boolean)
Main.SQLiteLoggingStatusByDB.Put(dbKey, isEnabled) ' Aplicamos el estado al mapa global
If isEnabled Then isAnyEnabled = True ' Calculamos el flag general
Next
' 3b. Controlar el Timer y el flag global
Main.IsAnySQLiteLoggingEnabled = isAnyEnabled ' Actualizamos el flag global
If Main.IsAnySQLiteLoggingEnabled Then
Main.timerLogs.Enabled = True
sb.Append($" -> Logs de SQLite HABILITADOS (Granular). Timer de limpieza ACTIVADO."$).Append(" " & CRLF)
Else
Main.timerLogs.Enabled = False
sb.Append($" -> Logs de SQLite DESHABILITADOS (Total). Timer de limpieza PERMANECERÁ DETENIDO."$).Append(" " & CRLF)
End If
' <<< FIN PASO CLAVE 3 >>>
sb.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append(" " & CRLF)
' ... (Resto del código: Mostrar estado de pools y Cierre explícito de oldConnectors) ...
Else
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de reemplazo de conectores! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
End If
Else ' Falla en inicialización (Punto 2)
' Si falla la recarga, restauramos el Timer al estado anterior.
If oldTimerState Then
Main.timerLogs.Enabled = True
sb.Append(" -> Restaurando Timer de limpieza de logs (SQLite) al estado ACTIVO debido a fallo en recarga.").Append(" " & CRLF)
End If
sb.Append($"¡ERROR: La recarga de configuración falló durante la inicialización de nuevos conectores! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
End If
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
sb.Append("<h2>Consultas Lentas Recientes</h2>")
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
sb.Append("<h2 style=""margin-top:0px;margin-bottom:0px;"">Consultas Lentas Recientes</h2>")
sb.Append("(Este registro depende de que los logs estén habilitados con del parámetro ""enableSQLiteLogs=1"" en config properties)<br><br>")
Try
' 1. Calcular el límite de tiempo: el tiempo actual (en milisegundos) menos 1 hora (3,600,000 ms).
Dim oneHourAgoMs As Long = DateTime.Now - 3600000
' Ajusta la consulta SQL para obtener las 20 queries más lentas.
' Utilizamos datetime con 'unixepoch' y 'localtime' para una visualización legible del timestamp.
Dim rs As ResultSet = Main.SQL1.ExecQuery("SELECT query_name, duration_ms, datetime(timestamp / 1000, 'unixepoch', 'localtime') as timestamp_local, db_key, client_ip, busy_connections, handler_active_requests FROM query_logs ORDER BY duration_ms DESC LIMIT 20")
Dim rs As ResultSet = Main.SQL1.ExecQuery($"SELECT query_name, duration_ms, datetime(timestamp / 1000, 'unixepoch', 'localtime') as timestamp_local, db_key, client_ip, busy_connections, handler_active_requests FROM query_logs WHERE timestamp >= ${oneHourAgoMs} ORDER BY duration_ms DESC LIMIT 20"$)
sb.Append("<table border='1' style='width:100%; text-align:left; border-collapse: collapse;'>")
sb.Append("<thead><tr><th>Query</th><th>Duración (ms)</th><th>Fecha/Hora Local</th><th>DB Key</th><th>Cliente IP</th><th>Conex. Ocupadas</th><th>Peticiones Activas</th></tr></thead>")