mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-17 21:06:24 +00:00
- 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:
25
Cambios.bas
25
Cambios.bas
@@ -13,9 +13,28 @@ Sub Process_Globals
|
|||||||
' - VERSION X.XX.XX (cambios a implementar)
|
' - VERSION X.XX.XX (cambios a implementar)
|
||||||
' - Agregar que se puedan usar cualquier cantidad de archivos config.properties
|
' - Agregar que se puedan usar cualquier cantidad de archivos config.properties
|
||||||
' - Agregar que se pueda recargar solo un archivo de configuracion o todos a la vez.
|
' - Agregar que se pueda recargar solo un archivo de configuracion o todos a la vez.
|
||||||
' - Agregar que el "Test" del manager revise (con el query de Jorge) cuantas conexiones hay actualmente activas,
|
' - Agregar una forma de probar con carga el servidor.
|
||||||
' - o si no en el test, un nuevo handler, talvez "Conexiones".
|
' - Agregar algun tipo de autenticación, posiblemente con tokens ... se podria poner
|
||||||
' - Agregar una forma de probar con carga el servidor
|
' en el config.properties un token de conexion y solo las peticiones que lo incluyan sean atendidas, o
|
||||||
|
' guardar los tokens en la BD sqlite.
|
||||||
|
' - Si se implementan los tokens, se podrian ligar los tokens con el "*" de CORS, y los tokens definirian
|
||||||
|
' los dominios permitidos.
|
||||||
|
' - Ej: token:1224abcd5678fghi, dominio:"keymon.net"
|
||||||
|
' - Ej: token:4321abcd8765fghi, dominio:"*"
|
||||||
|
' - Que los logs, en lugar de guardar de uno en uno en la BD Sqlte, se guarden en memoria, se junten ... por ejemplo 100 y ya que haya 100, se guarden
|
||||||
|
' en una solo query a la BD Sqlite.
|
||||||
|
' - Que en el reporte de "Queries lentos" se pueda especificar de cuanto tiempo, ahorita esta de la ultima hora, pero que se pueda seleccionar desde una
|
||||||
|
' lista, por ejemplo 15, 30, 45 y 60 minutos antes.
|
||||||
|
|
||||||
|
' - Versión: 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.
|
||||||
'
|
'
|
||||||
' - VERSION 5.09.16
|
' - VERSION 5.09.16
|
||||||
' feat: Implementa tolerancia de parámetros configurable y mejora estabilidad general del servidor.
|
' feat: Implementa tolerancia de parámetros configurable y mejora estabilidad general del servidor.
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
End If
|
End If
|
||||||
' === FIN DE LA LÓGICA DINÁMICA ===
|
' === FIN DE LA LÓGICA DINÁMICA ===
|
||||||
|
|
||||||
Log("********************* " & dbKey & " ********************") ' Log de depuración para identificar la base de datos.
|
' Log("********************* " & dbKey & " ********************") ' Log de depuración para identificar la base de datos.
|
||||||
|
|
||||||
Dim start As Long = DateTime.Now ' Registra el tiempo de inicio de la petición para calcular la duración.
|
Dim start As Long = DateTime.Now ' Registra el tiempo de inicio de la petición para calcular la duración.
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
End If
|
End If
|
||||||
' <<<< ¡FIN DE CAPTURA! >>>>
|
' <<<< ¡FIN DE CAPTURA! >>>>
|
||||||
|
|
||||||
Log("Metodo: " & method) ' Log de depuración para identificar el método de la petición.
|
' Log("Metodo: " & method) ' Log de depuración para identificar el método de la petición.
|
||||||
|
|
||||||
' --- Lógica para ejecutar diferentes tipos de comandos basados en el parámetro 'method' ---
|
' --- Lógica para ejecutar diferentes tipos de comandos basados en el parámetro 'method' ---
|
||||||
If method = "query2" Then
|
If method = "query2" Then
|
||||||
@@ -177,7 +177,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
' Este bloque se asegura de que, independientemente de cómo termine la petición (éxito o error),
|
' Este bloque se asegura de que, independientemente de cómo termine la petición (éxito o error),
|
||||||
' la duración se calcule y se llamen las subrutinas de limpieza y logging.
|
' la duración se calcule y se llamen las subrutinas de limpieza y logging.
|
||||||
duration = DateTime.Now - start ' Calcula la duración total de la petición.
|
duration = DateTime.Now - start ' Calcula la duración total de la petición.
|
||||||
Log($"Command: ${q}, took: ${duration}ms, client=${req.RemoteAddress}"$) ' Logea el comando y la duración.
|
Log($"${dbKey} - Command: ${q}, took: ${duration}ms, client=${req.RemoteAddress}"$) ' Logea el comando y la duración.
|
||||||
' Llama a la subrutina centralizada para registrar el rendimiento y limpiar recursos.
|
' Llama a la subrutina centralizada para registrar el rendimiento y limpiar recursos.
|
||||||
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ Private Sub CleanupAndLog(dbKey As String, qName As String, durMs As Long, clien
|
|||||||
Else
|
Else
|
||||||
' Si el contador ya está en 0 o negativo (lo cual no debería ocurrir con la lógica actual,
|
' Si el contador ya está en 0 o negativo (lo cual no debería ocurrir con la lógica actual,
|
||||||
' pero se maneja para robustez), registramos una advertencia y lo aseguramos en 0.
|
' pero se maneja para robustez), registramos una advertencia y lo aseguramos en 0.
|
||||||
Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
' Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
||||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
||||||
End If
|
End If
|
||||||
' Log($"[DEBUG] CleanupAndLog After Decrement (B4X): dbKey=${dbKey}, New count (as Int)=${GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey,0).As(Int)}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
' Log($"[DEBUG] CleanupAndLog After Decrement (B4X): dbKey=${dbKey}, New count (as Int)=${GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey,0).As(Int)}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||||
@@ -480,7 +480,7 @@ End Sub
|
|||||||
|
|
||||||
' Ejecuta una consulta única usando el protocolo V1.
|
' Ejecuta una consulta única usando el protocolo V1.
|
||||||
Private Sub ExecuteQuery(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
Private Sub ExecuteQuery(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
||||||
Log("====================== ExecuteQuery =====================")
|
' Log("====================== ExecuteQuery =====================")
|
||||||
' Deserializa los datos de la petición usando el protocolo V1.
|
' Deserializa los datos de la petición usando el protocolo V1.
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
Dim clientVersion As Float = ReadObject(in) 'ignore
|
||||||
Dim queryName As String = ReadObject(in)
|
Dim queryName As String = ReadObject(in)
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ Private Sub CleanupAndLog(dbKey As String, qName As String, durMs As Long, clien
|
|||||||
Else
|
Else
|
||||||
' Si el contador ya está en 0 o negativo (lo cual no debería ocurrir con la lógica actual,
|
' Si el contador ya está en 0 o negativo (lo cual no debería ocurrir con la lógica actual,
|
||||||
' pero se maneja para robustez), registramos una advertencia y lo aseguramos en 0.
|
' pero se maneja para robustez), registramos una advertencia y lo aseguramos en 0.
|
||||||
Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
' Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
||||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
||||||
End If
|
End If
|
||||||
' Log($"[DEBUG] CleanupAndLog After Decrement (JSON): dbKey=${dbKey}, New count (as Int)=${GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey,0).As(Int)}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
' Log($"[DEBUG] CleanupAndLog After Decrement (JSON): dbKey=${dbKey}, New count (as Int)=${GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey,0).As(Int)}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||||
|
|||||||
193
Manager.bas
193
Manager.bas
@@ -49,7 +49,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
' toolkit.InitializeStatic("java.awt.Toolkit")
|
' toolkit.InitializeStatic("java.awt.Toolkit")
|
||||||
' Dim screenRect As JavaObject
|
' Dim screenRect As JavaObject
|
||||||
' screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
|
' 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 image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect))
|
||||||
' Dim ImageIO As JavaObject
|
' Dim ImageIO As JavaObject
|
||||||
' ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream))
|
' 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 ###
|
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
|
||||||
' =========================================================================
|
' =========================================================================
|
||||||
If Command = "reload" Then
|
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
|
Dim sbTemp As StringBuilder
|
||||||
sbTemp.Initialize
|
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.
|
' 1. Crear un nuevo mapa temporal para almacenar los conectores recién inicializados.
|
||||||
Dim newConnectors As Map
|
Dim newConnectors As Map
|
||||||
newConnectors.Initialize
|
newConnectors.Initialize
|
||||||
|
|
||||||
' Guardamos una referencia al mapa de conectores actualmente activos.
|
|
||||||
Dim oldConnectors As Map
|
Dim oldConnectors As Map
|
||||||
|
|
||||||
Dim reloadSuccessful As Boolean = True
|
Dim reloadSuccessful As Boolean = True
|
||||||
|
|
||||||
' *** INICIO DEL BLOQUE CRÍTICO 1: Obtener oldConnectors con ReentrantLock ***
|
' *** 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
|
Try
|
||||||
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||||
lock1Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
lock1Acquired = True
|
||||||
oldConnectors = Main.Connectors ' Obtenemos la referencia al mapa actual de conectores.
|
oldConnectors = Main.Connectors
|
||||||
Catch
|
Catch
|
||||||
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append("<br>" & CRLF)
|
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append(" " & CRLF)
|
||||||
reloadSuccessful = False ' Si falla aquí, la recarga no puede continuar.
|
reloadSuccessful = False
|
||||||
End Try
|
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.
|
If lock1Acquired Then
|
||||||
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados al StringBuilder principal.
|
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
End If
|
||||||
Return ' Salir del Handle ya que ocurrió un error crítico irrecuperable.
|
|
||||||
|
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
|
End If
|
||||||
|
|
||||||
' 2. Iterar sobre las bases de datos configuradas y crear *nuevas* instancias de RDCConnector.
|
' 2. Iterar sobre las bases de datos configuradas y crear *nuevas* instancias de RDCConnector.
|
||||||
For Each dbKey As String In Main.listaDeCP
|
For Each dbKey As String In Main.listaDeCP
|
||||||
|
|
||||||
Try
|
Try
|
||||||
Dim newRDC As RDCConnector
|
Dim newRDC As RDCConnector
|
||||||
newRDC.Initialize(dbKey) ' Inicializa la nueva instancia con la configuración fresca.
|
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)
|
newConnectors.Put(dbKey, newRDC)
|
||||||
|
|
||||||
Dim newPoolStats As Map = newRDC.GetPoolStats
|
Dim newPoolStats As Map = newRDC.GetPoolStats
|
||||||
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append("<br>" & CRLF)
|
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append(" " & CRLF)
|
||||||
|
|
||||||
Catch
|
Catch
|
||||||
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("<br>" & CRLF)
|
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append(" " & CRLF)
|
||||||
reloadSuccessful = False
|
reloadSuccessful = False
|
||||||
Exit ' Si uno falla, abortamos la recarga completa para evitar un estado inconsistente.
|
Exit ' Si uno falla, abortamos la recarga.
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
Next
|
Next
|
||||||
|
|
||||||
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados de la inicialización al StringBuilder principal.
|
sb.Append(sbTemp.ToString)
|
||||||
|
|
||||||
If reloadSuccessful Then
|
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 ***
|
' *** 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
|
Try
|
||||||
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||||
lock2Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
lock2Acquired = True
|
||||||
Main.Connectors = newConnectors ' Reemplazamos el mapa de conectores completo por el nuevo.
|
Main.Connectors = newConnectors ' Reemplazamos el mapa de conectores completo por el nuevo.
|
||||||
Catch
|
Catch
|
||||||
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append("<br>" & CRLF)
|
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append(" " & CRLF)
|
||||||
reloadSuccessful = False ' Si falla aquí, la recarga no se completó con éxito.
|
reloadSuccessful = False
|
||||||
End Try
|
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 lock2Acquired Then
|
||||||
If oldConnectors.IsInitialized Then
|
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
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)
|
|
||||||
End If
|
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)
|
If reloadSuccessful Then ' Si el swap fue exitoso
|
||||||
' Si la recarga falló, los conectores antiguos (oldConnectors) se mantienen activos
|
|
||||||
' y siguen sirviendo para evitar un paro del servicio.
|
' <<< 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
|
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
|
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.
|
' 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.
|
' 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("<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>")
|
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>")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Public Sub ValidateAndAdjustParameters (CommandName As String, DBKey As String,
|
|||||||
res.Initialize
|
res.Initialize
|
||||||
res.Success = True ' Asumimos éxito inicialmente
|
res.Success = True ' Asumimos éxito inicialmente
|
||||||
|
|
||||||
Log(">>>> IsToleranceEnabled: " & IsToleranceEnabled)
|
' Log(">>>> IsToleranceEnabled: " & IsToleranceEnabled)
|
||||||
|
|
||||||
' Aseguramos que receivedParams esté inicializada, incluso si está vacía o Null
|
' Aseguramos que receivedParams esté inicializada, incluso si está vacía o Null
|
||||||
If receivedParams = Null Or receivedParams.IsInitialized = False Then
|
If receivedParams = Null Or receivedParams.IsInitialized = False Then
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Sub Class_Globals
|
|||||||
|
|
||||||
' Bandera para activar/desactivar el modo de depuración de queries.
|
' Bandera para activar/desactivar el modo de depuración de queries.
|
||||||
' Cuando está en True, los comandos SQL se recargan en cada petición (útil en desarrollo).
|
' Cuando está en True, los comandos SQL se recargan en cada petición (útil en desarrollo).
|
||||||
Private DebugQueries As Boolean
|
' Private DebugQueries As Boolean
|
||||||
|
|
||||||
' Almacena los comandos SQL específicos de esta base de datos, cargados de su archivo de configuración.
|
' Almacena los comandos SQL específicos de esta base de datos, cargados de su archivo de configuración.
|
||||||
Public commands As Map
|
Public commands As Map
|
||||||
@@ -157,11 +157,11 @@ Public Sub Initialize(DB As String)
|
|||||||
End Try
|
End Try
|
||||||
|
|
||||||
' Configuración de depuración de queries. Se activa automáticamente si el proyecto se ejecuta en modo DEBUG.
|
' Configuración de depuración de queries. Se activa automáticamente si el proyecto se ejecuta en modo DEBUG.
|
||||||
#If DEBUG
|
' #If DEBUG
|
||||||
' DebugQueries = True ' Descomentar para activar la recarga de comandos en cada petición en desarrollo.
|
' DebugQueries = True ' Descomentar para activar la recarga de comandos en cada petición en desarrollo.
|
||||||
#Else
|
' #Else
|
||||||
DebugQueries = False
|
' DebugQueries = False
|
||||||
#End If
|
' #End If
|
||||||
|
|
||||||
' Se obtiene el puerto del servidor HTTP desde la configuración de esta base de datos.
|
' Se obtiene el puerto del servidor HTTP desde la configuración de esta base de datos.
|
||||||
' Nota: En el diseño actual, el puerto principal lo define DB1 (config.properties).
|
' Nota: En el diseño actual, el puerto principal lo define DB1 (config.properties).
|
||||||
@@ -208,7 +208,7 @@ Public Sub GetConnection(DB As String) As SQL
|
|||||||
|
|
||||||
' En modo de depuración, recarga los comandos SQL en cada petición.
|
' En modo de depuración, recarga los comandos SQL en cada petición.
|
||||||
' Esto permite modificar queries en config.properties sin reiniciar el servidor durante el desarrollo.
|
' Esto permite modificar queries en config.properties sin reiniciar el servidor durante el desarrollo.
|
||||||
If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB)
|
' If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB)
|
||||||
|
|
||||||
' <<<< Bloque de Logs de Depuración de Adquisición de Conexión (descomentar si es necesario) >>>>
|
' <<<< Bloque de Logs de Depuración de Adquisición de Conexión (descomentar si es necesario) >>>>
|
||||||
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Solicitando conexión del pool..."$)
|
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Solicitando conexión del pool..."$)
|
||||||
|
|||||||
341
jRDC_Multi.b4j
341
jRDC_Multi.b4j
@@ -52,6 +52,7 @@ Version=10.3
|
|||||||
'Non-UI application (console / server application)
|
'Non-UI application (console / server application)
|
||||||
|
|
||||||
#Region Project Attributes
|
#Region Project Attributes
|
||||||
|
|
||||||
#CommandLineArgs:
|
#CommandLineArgs:
|
||||||
#MergeLibraries: True
|
#MergeLibraries: True
|
||||||
' VERSION 5.09.16
|
' VERSION 5.09.16
|
||||||
@@ -76,105 +77,110 @@ Version=10.3
|
|||||||
|
|
||||||
Sub Process_Globals
|
Sub Process_Globals
|
||||||
' --- Variables globales accesibles desde cualquier parte del proyecto ---
|
' --- Variables globales accesibles desde cualquier parte del proyecto ---
|
||||||
|
|
||||||
' Objeto principal del servidor HTTP de B4J.
|
' Objeto principal del servidor HTTP de B4J.
|
||||||
Public srvr As Server
|
Public srvr As Server
|
||||||
|
|
||||||
' La versión actual de este servidor jRDC modificado.
|
' La versión actual de este servidor jRDC modificado.
|
||||||
Public const VERSION As Float = 2.23
|
Public const VERSION As Float = 2.23
|
||||||
|
|
||||||
' Tipos personalizados (clases) para la serialización y deserialización de datos
|
' Tipos personalizados (clases) para la serialización y deserialización de datos
|
||||||
' entre el cliente B4X (DBRequestManager) y el servidor jRDC2.
|
Type DBCommand (Name As String, Parameters() As Object)
|
||||||
Type DBCommand (Name As String, Parameters() As Object) ' Define un comando SQL.
|
Type DBResult (Tag As Object, Columns As Map, Rows As List)
|
||||||
Type DBResult (Tag As Object, Columns As Map, Rows As List) ' Define la estructura de un resultado de consulta.
|
|
||||||
|
|
||||||
' Contiene una lista de los identificadores de bases de datos configuradas (ej. "DB1", "DB2").
|
' Contiene una lista de los identificadores de bases de datos configuradas (ej. "DB1", "DB2").
|
||||||
Public listaDeCP As List
|
Public listaDeCP As List
|
||||||
|
|
||||||
' Una lista temporal para almacenar los nombres de archivos de configuración encontrados.
|
' Una lista temporal para almacenar los nombres de archivos de configuración encontrados.
|
||||||
Private cpFiles As List
|
Private cpFiles As List
|
||||||
|
|
||||||
' Mapas globales para gestionar los conectores de base de datos y los comandos SQL.
|
' Mapas globales para gestionar los conectores de base de datos y los comandos SQL.
|
||||||
' Connectors: Almacena las instancias de RDCConnector por cada base de datos (DB1, DB2, etc.).
|
|
||||||
' commandsMap: Almacena los comandos SQL cargados de los archivos de configuración para cada DB.
|
|
||||||
Public Connectors, commandsMap As Map
|
Public Connectors, commandsMap As Map
|
||||||
|
|
||||||
' Objeto SQL para interactuar con la base de datos de usuarios y logs (SQLite).
|
' Objeto SQL para interactuar con la base de datos de usuarios y logs (SQLite).
|
||||||
Public SQL1 As SQL
|
Public SQL1 As SQL
|
||||||
|
|
||||||
' Objeto para realizar operaciones de hashing de contraseñas de forma segura (para autenticación de Manager).
|
' Objeto para realizar operaciones de hashing de contraseñas de forma segura.
|
||||||
Private bc As BCrypt
|
Private bc As BCrypt
|
||||||
|
|
||||||
' Objeto de bloqueo (ReentrantLock) para proteger el mapa Main.Connectors durante operaciones de recarga (Hot-Swap).
|
' Objeto de bloqueo (ReentrantLock) para proteger Main.Connectors durante Hot-Swap.
|
||||||
Public MainConnectorsLock As JavaObject
|
Public MainConnectorsLock As JavaObject
|
||||||
|
|
||||||
' Timer para ejecutar tareas periódicas, como la limpieza de logs.
|
' Timer para ejecutar tareas periódicas, como la limpieza de logs.
|
||||||
Public timerLogs As Timer
|
Public timerLogs As Timer
|
||||||
|
|
||||||
|
' NUEVAS VARIABLES para control granular de logs
|
||||||
|
' Mapa para almacenar el estado de logging (True/False) por cada DBKey (DB1, DB2, etc.).
|
||||||
|
Public SQLiteLoggingStatusByDB As Map
|
||||||
|
' Bandera global que indica si AL MENOS una base de datos tiene los logs habilitados.
|
||||||
|
Public IsAnySQLiteLoggingEnabled As Boolean
|
||||||
|
|
||||||
' Tipo para encapsular el resultado de la validación de parámetros.
|
' Tipo para encapsular el resultado de la validación de parámetros.
|
||||||
Type ParameterValidationResult ( _
|
Type ParameterValidationResult ( _
|
||||||
Success As Boolean, _
|
Success As Boolean, _
|
||||||
ErrorMessage As String, _
|
ErrorMessage As String, _
|
||||||
ParamsToExecute As List _ ' La lista de parámetros final a usar en la ejecución SQL
|
ParamsToExecute As List _ ' La lista de parámetros final a usar en la ejecución SQL
|
||||||
)
|
)
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Sub AppStart (Args() As String)
|
Sub AppStart (Args() As String)
|
||||||
' --- Subrutina principal que se ejecuta al iniciar la aplicación ---
|
' --- Subrutina principal que se ejecuta al iniciar la aplicación ---
|
||||||
|
|
||||||
bc.Initialize("BC")
|
bc.Initialize("BC")
|
||||||
|
|
||||||
' 1. Inicializa la base de datos local de usuarios (SQLite) y la tabla de logs.
|
' 1. Inicializa la base de datos local de usuarios (SQLite) y la tabla de logs.
|
||||||
' Esta base de datos se crea automáticamente si no existe o se migra si es necesario.
|
|
||||||
InitializeSQLiteDatabase
|
InitializeSQLiteDatabase
|
||||||
|
|
||||||
' <<<< Bloque de inicialización del Timer para la limpieza de logs >>>>
|
|
||||||
' Inicializa y configura el Timer para borrar logs antiguos cada 10 minutos (600,000 milisegundos).
|
|
||||||
timerLogs.Initialize("TimerLogs", 600000) ' 10 minutos = 600 * 1000 = 600000 ms
|
|
||||||
timerLogs.Enabled = True ' Habilita el timer para que empiece a correr.
|
|
||||||
Log("Main.AppStart: Timer de limpieza de 'query_logs' inicializado para ejecutarse cada 10 minutos.")
|
|
||||||
' <<<< Fin del bloque del Timer >>>>
|
|
||||||
|
|
||||||
' 2. Inicializa los mapas globales definidos en GlobalParameters.bas.
|
' 2. Inicializa los mapas globales definidos en GlobalParameters.bas.
|
||||||
' Estos mapas se usan para monitorear el servidor y gestionar configuraciones dinámicas.
|
GlobalParameters.mpLogs.Initialize
|
||||||
GlobalParameters.mpLogs.Initialize ' Mapa para almacenar logs de actividad general.
|
GlobalParameters.mpTotalRequests.Initialize
|
||||||
GlobalParameters.mpTotalRequests.Initialize ' Mapa para contar peticiones por endpoint/DB.
|
GlobalParameters.mpTotalConnections.Initialize
|
||||||
GlobalParameters.mpTotalConnections.Initialize ' Mapa para almacenar el estado de los pools de conexión por DB.
|
GlobalParameters.mpBlockConnection.Initialize
|
||||||
GlobalParameters.mpBlockConnection.Initialize ' Mapa para gestionar IPs bloqueadas (si la funcionalidad está activa).
|
|
||||||
' Aseguramos que el mapa de conteo de peticiones activas sea thread-safe para un manejo concurrente seguro.
|
' Aseguramos que el mapa de conteo de peticiones activas sea thread-safe.
|
||||||
GlobalParameters.ActiveRequestsCountByDB = srvr.CreateThreadSafeMap
|
GlobalParameters.ActiveRequestsCountByDB = srvr.CreateThreadSafeMap
|
||||||
|
|
||||||
' 3. Inicializa las estructuras principales del servidor HTTP.
|
' 3. Inicializa las estructuras principales del servidor HTTP.
|
||||||
listaDeCP.Initialize ' Inicializa la lista que contendrá los IDs de las bases de datos.
|
listaDeCP.Initialize
|
||||||
srvr.Initialize("") ' Inicializa el objeto servidor HTTP.
|
srvr.Initialize("")
|
||||||
Connectors = srvr.CreateThreadSafeMap ' Crea un mapa seguro para almacenar instancias de RDCConnector (un conector por DB).
|
Connectors = srvr.CreateThreadSafeMap
|
||||||
commandsMap.Initialize ' Inicializa el mapa que almacenará los comandos SQL cargados de los archivos de configuración.
|
commandsMap.Initialize
|
||||||
|
|
||||||
' Creamos una instancia de ReentrantLock para proteger Main.Connectors durante operaciones atómicas de Hot-Swap.
|
' NUEVO: Inicializar el mapa de estado de logs granular
|
||||||
|
SQLiteLoggingStatusByDB.Initialize
|
||||||
|
|
||||||
|
' Creamos una instancia de ReentrantLock para proteger Main.Connectors.
|
||||||
MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)
|
MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)
|
||||||
|
|
||||||
|
|
||||||
' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) ===
|
' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) ===
|
||||||
' DB1 siempre usa el archivo 'config.properties' por defecto.
|
|
||||||
Try
|
Try
|
||||||
Dim con1 As RDCConnector ' Declara una variable específica y única para el conector de DB1.
|
Dim con1 As RDCConnector
|
||||||
con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1".
|
con1.Initialize("DB1")
|
||||||
Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector.
|
Connectors.Put("DB1", con1)
|
||||||
srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1.
|
srvr.Port = con1.serverPort
|
||||||
listaDeCP.Add("DB1") ' Añade "DB1" a la lista de bases de datos gestionadas.
|
listaDeCP.Add("DB1")
|
||||||
Log($"Main.AppStart: Conector 'DB1' inicializado exitosamente en puerto: ${srvr.Port}"$)
|
Log($"Main.AppStart: Conector 'DB1' inicializado exitosamente en puerto: ${srvr.Port}"$)
|
||||||
|
|
||||||
|
' Lógica de Logs para DB1 (Fuente principal de configuración)
|
||||||
|
Dim enableLogsSetting As Int = con1.config.GetDefault("enableSQLiteLogs", 1).As(Int)
|
||||||
|
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||||
|
SQLiteLoggingStatusByDB.Put("DB1", isEnabled) ' Guardar el estado
|
||||||
|
|
||||||
Catch
|
Catch
|
||||||
Dim ErrorMsg As String = $"Main.AppStart: ERROR CRÍTICO al inicializar conector 'DB1': ${LastException.Message}"$
|
Dim ErrorMsg As String = $"Main.AppStart: ERROR CRÍTICO al inicializar conector 'DB1': ${LastException.Message}"$
|
||||||
Log(ErrorMsg)
|
Log(ErrorMsg)
|
||||||
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB1", Null, Null)
|
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB1", Null, Null)
|
||||||
' Si DB1 falla, el servidor no puede arrancar correctamente.
|
|
||||||
ExitApplication
|
ExitApplication
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
' === 5. DETECCIÓN E INICIALIZACIÓN DE BASES DE DATOS ADICIONALES (DB2, DB3, DB4) ===
|
' === 5. DETECCIÓN E INICIALIZACIÓN DE BASES DE DATOS ADICIONALES (DB2, DB3, DB4) ===
|
||||||
' El servidor busca archivos de configuración adicionales (ej. config.DB2.properties)
|
|
||||||
' en el mismo directorio donde se ejecuta el JAR.
|
|
||||||
cpFiles = File.ListFiles("./")
|
cpFiles = File.ListFiles("./")
|
||||||
If cpFiles.Size > 0 Then
|
If cpFiles.Size > 0 Then
|
||||||
For i = 0 To cpFiles.Size - 1
|
For i = 0 To cpFiles.Size - 1
|
||||||
|
|
||||||
' Procesa 'config.DB2.properties'
|
' Procesa 'config.DB2.properties'
|
||||||
If cpFiles.Get(i) = "config.DB2.properties" Then
|
If cpFiles.Get(i) = "config.DB2.properties" Then
|
||||||
Try
|
Try
|
||||||
@@ -183,13 +189,19 @@ Sub AppStart (Args() As String)
|
|||||||
Connectors.Put("DB2", con2)
|
Connectors.Put("DB2", con2)
|
||||||
listaDeCP.Add("DB2")
|
listaDeCP.Add("DB2")
|
||||||
Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.")
|
Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.")
|
||||||
|
|
||||||
|
' Lógica de Logs para DB2
|
||||||
|
Dim enableLogsSetting As Int = con2.config.GetDefault("enableSQLiteLogs", 0).As(Int)
|
||||||
|
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||||
|
SQLiteLoggingStatusByDB.Put("DB2", isEnabled) ' Guardar el estado
|
||||||
|
|
||||||
Catch
|
Catch
|
||||||
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB2': ${LastException.Message}"$
|
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB2': ${LastException.Message}"$
|
||||||
Log(ErrorMsg)
|
Log(ErrorMsg)
|
||||||
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB2", Null, Null)
|
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB2", Null, Null)
|
||||||
' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo.
|
|
||||||
End Try
|
End Try
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' Procesa 'config.DB3.properties'
|
' Procesa 'config.DB3.properties'
|
||||||
If cpFiles.Get(i) = "config.DB3.properties" Then
|
If cpFiles.Get(i) = "config.DB3.properties" Then
|
||||||
Try
|
Try
|
||||||
@@ -198,13 +210,19 @@ Sub AppStart (Args() As String)
|
|||||||
Connectors.Put("DB3", con3)
|
Connectors.Put("DB3", con3)
|
||||||
listaDeCP.Add("DB3")
|
listaDeCP.Add("DB3")
|
||||||
Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.")
|
Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.")
|
||||||
|
|
||||||
|
' Lógica de Logs para DB3
|
||||||
|
Dim enableLogsSetting As Int = con3.config.GetDefault("enableSQLiteLogs", 0).As(Int)
|
||||||
|
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||||
|
SQLiteLoggingStatusByDB.Put("DB3", isEnabled) ' Guardar el estado
|
||||||
|
|
||||||
Catch
|
Catch
|
||||||
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB3': ${LastException.Message}"$
|
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB3': ${LastException.Message}"$
|
||||||
Log(ErrorMsg)
|
Log(ErrorMsg)
|
||||||
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB3", Null, Null)
|
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB3", Null, Null)
|
||||||
' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo.
|
|
||||||
End Try
|
End Try
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' Procesa 'config.DB4.properties'
|
' Procesa 'config.DB4.properties'
|
||||||
If cpFiles.Get(i) = "config.DB4.properties" Then
|
If cpFiles.Get(i) = "config.DB4.properties" Then
|
||||||
Try
|
Try
|
||||||
@@ -213,18 +231,24 @@ Sub AppStart (Args() As String)
|
|||||||
Connectors.Put("DB4", con4)
|
Connectors.Put("DB4", con4)
|
||||||
listaDeCP.Add("DB4")
|
listaDeCP.Add("DB4")
|
||||||
Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.")
|
Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.")
|
||||||
|
|
||||||
|
' Lógica de Logs para DB4
|
||||||
|
Dim enableLogsSetting As Int = con4.config.GetDefault("enableSQLiteLogs", 0).As(Int)
|
||||||
|
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||||
|
SQLiteLoggingStatusByDB.Put("DB4", isEnabled) ' Guardar el estado
|
||||||
|
|
||||||
Catch
|
Catch
|
||||||
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB4': ${LastException.Message}"$
|
Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB4': ${LastException.Message}"$
|
||||||
Log(ErrorMsg)
|
Log(ErrorMsg)
|
||||||
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB4", Null, Null)
|
LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB4", Null, Null)
|
||||||
' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo.
|
|
||||||
End Try
|
End Try
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Next
|
Next
|
||||||
Else
|
Else
|
||||||
Log("Main.AppStart: No se encontraron archivos de configuración adicionales (config.DBx.properties).")
|
Log("Main.AppStart: No se encontraron archivos de configuración adicionales (config.DBx.properties).")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' Log final de las bases de datos que el servidor está gestionando.
|
' Log final de las bases de datos que el servidor está gestionando.
|
||||||
Dim sbListaDeCP_Log As StringBuilder
|
Dim sbListaDeCP_Log As StringBuilder
|
||||||
sbListaDeCP_Log.Initialize
|
sbListaDeCP_Log.Initialize
|
||||||
@@ -232,26 +256,46 @@ Sub AppStart (Args() As String)
|
|||||||
sbListaDeCP_Log.Append(item).Append(", ")
|
sbListaDeCP_Log.Append(item).Append(", ")
|
||||||
Next
|
Next
|
||||||
If sbListaDeCP_Log.Length > 0 Then
|
If sbListaDeCP_Log.Length > 0 Then
|
||||||
sbListaDeCP_Log.Remove(sbListaDeCP_Log.Length - 2, sbListaDeCP_Log.Length) ' Elimina la última ", "
|
sbListaDeCP_Log.Remove(sbListaDeCP_Log.Length - 2, sbListaDeCP_Log.Length)
|
||||||
End If
|
End If
|
||||||
Log($"Main.AppStart: Bases de datos configuradas y listas: [${sbListaDeCP_Log.ToString}]"$)
|
Log($"Main.AppStart: Bases de datos configuradas y listas: [${sbListaDeCP_Log.ToString}]"$)
|
||||||
|
|
||||||
|
' <<<< Bloque de inicialización del Timer para la limpieza de logs >>>>
|
||||||
|
|
||||||
|
' Inicialización INCONDICIONAL del Timer (Garantiza que el objeto exista y prevenga el IllegalStateException)
|
||||||
|
timerLogs.Initialize("TimerLogs", 600000) ' 10 minutos = 600 * 1000 = 600000 ms
|
||||||
|
|
||||||
|
' CONTROL CONDICIONAL BASADO EN EL ESTADO GRANULAR
|
||||||
|
IsAnySQLiteLoggingEnabled = False
|
||||||
|
For Each dbStatus As Boolean In SQLiteLoggingStatusByDB.Values
|
||||||
|
If dbStatus Then
|
||||||
|
IsAnySQLiteLoggingEnabled = True
|
||||||
|
Exit ' Si uno está activo, es suficiente para encender el Timer
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
If IsAnySQLiteLoggingEnabled Then
|
||||||
|
timerLogs.Enabled = True
|
||||||
|
Log("Main.AppStart: Timer de limpieza de logs ACTIVADO (al menos una DB requiere logs).")
|
||||||
|
Else
|
||||||
|
timerLogs.Enabled = False
|
||||||
|
Log("Main.AppStart: Timer de limpieza de logs DESHABILITADO (ninguna DB requiere logs).")
|
||||||
|
End If
|
||||||
|
|
||||||
|
' <<<< Fin del bloque del Timer >>>>
|
||||||
|
|
||||||
' === 6. REGISTRO DE HANDLERS HTTP PARA EL SERVIDOR ===
|
' === 6. REGISTRO DE HANDLERS HTTP PARA EL SERVIDOR ===
|
||||||
' Asocia rutas URL específicas con clases que manejarán las peticiones correspondientes.
|
srvr.AddHandler("/ping", "ping", False)
|
||||||
' El último parámetro (True) indica que el handler se ejecutará en un nuevo hilo,
|
srvr.AddHandler("/test", "TestHandler", False)
|
||||||
' lo que es recomendable para la mayoría de los casos para evitar bloqueos.
|
srvr.AddHandler("/login", "LoginHandler", False)
|
||||||
srvr.AddHandler("/ping", "ping", False) ' Endpoint simple para verificar si el servidor está activo.
|
srvr.AddHandler("/dologin", "DoLoginHandler", False)
|
||||||
srvr.AddHandler("/test", "TestHandler", False) ' Endpoint para pruebas de conexión y estado del servidor.
|
srvr.AddHandler("/logout", "LogoutHandler", False)
|
||||||
srvr.AddHandler("/login", "LoginHandler", False) ' Muestra la página HTML de login.
|
srvr.AddHandler("/changepass", "ChangePassHandler", False)
|
||||||
srvr.AddHandler("/dologin", "DoLoginHandler", False) ' Procesa el intento de inicio de sesión.
|
srvr.AddHandler("/manager", "Manager", False)
|
||||||
srvr.AddHandler("/logout", "LogoutHandler", False) ' Cierra la sesión del usuario.
|
srvr.AddHandler("/DBJ", "DBHandlerJSON", False)
|
||||||
srvr.AddHandler("/changepass", "ChangePassHandler", False) ' Permite a los usuarios cambiar su contraseña.
|
srvr.AddHandler("/dbrquery", "DBHandlerJSON", False)
|
||||||
srvr.AddHandler("/manager", "Manager", False) ' Panel de administración del servidor (requiere autenticación).
|
srvr.AddHandler("/favicon.ico", "faviconHandler", False)
|
||||||
srvr.AddHandler("/DBJ", "DBHandlerJSON", False) ' Handler para clientes web (ej. JavaScript, Node.js) que usan JSON.
|
srvr.AddHandler("/*", "DBHandlerB4X", False)
|
||||||
srvr.AddHandler("/dbrquery", "DBHandlerJSON", False) ' Un alias para el handler JSON, por si se usa en clientes específicos.
|
|
||||||
srvr.AddHandler("/favicon.ico", "faviconHandler", False) ' Sirve el icono de la página (favicon).
|
|
||||||
srvr.AddHandler("/*", "DBHandlerB4X", False) ' Handler por defecto para clientes B4X (DBRequestManager),
|
|
||||||
' procesa peticiones dinámicamente según la URL.
|
|
||||||
|
|
||||||
' 7. Inicia el servidor HTTP.
|
' 7. Inicia el servidor HTTP.
|
||||||
srvr.Start
|
srvr.Start
|
||||||
@@ -259,81 +303,73 @@ Sub AppStart (Args() As String)
|
|||||||
Log($"-=== jRDC está funcionando en el puerto: ${srvr.Port} (versión = $1.2{VERSION}) ===-"$)
|
Log($"-=== jRDC está funcionando en el puerto: ${srvr.Port} (versión = $1.2{VERSION}) ===-"$)
|
||||||
Log("===========================================================")
|
Log("===========================================================")
|
||||||
|
|
||||||
' 8. Inicia el bucle de mensajes de B4J. Es esencial para que la aplicación
|
' 8. Inicia el bucle de mensajes de B4J.
|
||||||
' de servidor continúe ejecutándose y procesando eventos.
|
|
||||||
StartMessageLoop
|
StartMessageLoop
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' --- Subrutina para inicializar la base de datos de usuarios local (SQLite) ---
|
' --- Subrutina para inicializar la base de datos de usuarios local (SQLite) ---
|
||||||
' Esta base de datos se utiliza para almacenar credenciales de usuarios que pueden
|
|
||||||
' acceder al panel de administración del servidor jRDC y los logs de queries.
|
|
||||||
Sub InitializeSQLiteDatabase
|
|
||||||
Dim dbFileName As String = "users.db" ' Nombre del archivo de la base de datos SQLite.
|
|
||||||
|
|
||||||
' Verifica si el archivo de la base de datos ya existe en el directorio de la aplicación.
|
Sub InitializeSQLiteDatabase
|
||||||
|
Dim dbFileName As String = "users.db"
|
||||||
|
|
||||||
If File.Exists(File.DirApp, dbFileName) = False Then
|
If File.Exists(File.DirApp, dbFileName) = False Then
|
||||||
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
||||||
' Inicializa la conexión a la base de datos SQLite, creándola si no existe (último parámetro en True).
|
|
||||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||||
|
|
||||||
' Define y ejecuta la sentencia SQL para crear la tabla 'users' para la autenticación del Manager.
|
' Crear tabla 'users'
|
||||||
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
||||||
SQL1.ExecNonQuery(createUserTable)
|
SQL1.ExecNonQuery(createUserTable)
|
||||||
|
|
||||||
' >>> INICIO: Creación de la tabla query_logs con las nuevas columnas desde CERO <<<
|
' Crear tabla 'query_logs'
|
||||||
Log("Creando tabla 'query_logs' con columnas de rendimiento.")
|
Log("Creando tabla 'query_logs' con columnas de rendimiento.")
|
||||||
' Esta tabla almacena métricas detalladas de cada query ejecutada.
|
|
||||||
Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)"
|
Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)"
|
||||||
SQL1.ExecNonQuery(createQueryLogsTable)
|
SQL1.ExecNonQuery(createQueryLogsTable)
|
||||||
' >>> FIN: Creación de la tabla query_logs <<<
|
|
||||||
|
' Insertar usuario por defecto
|
||||||
' Crea un usuario por defecto para facilitar el primer acceso al panel de administración.
|
|
||||||
Dim defaultUser As String = "admin"
|
Dim defaultUser As String = "admin"
|
||||||
Dim defaultPass As String = "12345"
|
Dim defaultPass As String = "12345"
|
||||||
' Genera un hash seguro de la contraseña usando BCrypt, lo cual es crucial para la seguridad.
|
|
||||||
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt)
|
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt)
|
||||||
' Inserta el usuario por defecto en la tabla 'users'.
|
|
||||||
SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass))
|
SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass))
|
||||||
Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$)
|
Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$)
|
||||||
|
|
||||||
Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)"
|
' Crear tabla 'errores'
|
||||||
SQL1.ExecNonQuery(createQueryLogsTable)
|
|
||||||
|
|
||||||
' >>> INICIO: Creación de la tabla errores con columnas de error/advertencia <<<
|
|
||||||
Log("Creando tabla 'errores' para registrar eventos.")
|
Log("Creando tabla 'errores' para registrar eventos.")
|
||||||
Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)"
|
Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)"
|
||||||
SQL1.ExecNonQuery(createErrorsTable)
|
SQL1.ExecNonQuery(createErrorsTable)
|
||||||
' >>> FIN: Creación de la tabla errores <<<
|
|
||||||
Else
|
Else
|
||||||
' Si el archivo de la base de datos ya existe, simplemente se abre.
|
|
||||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||||
Log("Base de datos de usuarios cargada.")
|
Log("Base de datos de usuarios cargada.")
|
||||||
|
|
||||||
' >>> INICIO: Lógica de migración (ALTER TABLE) si la DB ya existía <<<
|
' >>> INICIO: Lógica de migración (ALTER TABLE) si la DB ya existía <<<
|
||||||
Log("Verificando y migrando tabla 'query_logs' si es necesario.")
|
Log("Verificando y migrando tabla 'query_logs' si es necesario.")
|
||||||
|
|
||||||
' Primero, verificar si la tabla query_logs existe.
|
|
||||||
If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs'") = Null Then
|
If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs'") = Null Then
|
||||||
|
|
||||||
Log("Tabla 'query_logs' no encontrada, creándola con columnas de rendimiento.")
|
Log("Tabla 'query_logs' no encontrada, creándola con columnas de rendimiento.")
|
||||||
|
|
||||||
Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)"
|
Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)"
|
||||||
|
|
||||||
SQL1.ExecNonQuery(createQueryLogsTable)
|
SQL1.ExecNonQuery(createQueryLogsTable)
|
||||||
|
|
||||||
Else
|
Else
|
||||||
|
|
||||||
' Si la tabla query_logs ya existe, entonces verificamos y añadimos las columnas faltantes (busy_connections, handler_active_requests).
|
' Si la tabla query_logs ya existe, entonces verificamos y añadimos las columnas faltantes (busy_connections, handler_active_requests).
|
||||||
Dim columnExists As Boolean
|
Dim columnExists As Boolean
|
||||||
Dim rs As ResultSet
|
Dim rs As ResultSet
|
||||||
|
|
||||||
' --- VERIFICAR Y AÑADIR busy_connections ---
|
' --- VERIFICAR Y AÑADIR busy_connections ---
|
||||||
columnExists = False
|
columnExists = False
|
||||||
' Ejecutamos PRAGMA para obtener la información de la tabla y verificar la existencia de la columna.
|
|
||||||
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)")
|
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)")
|
||||||
|
|
||||||
Do While rs.NextRow
|
Do While rs.NextRow
|
||||||
If rs.GetString("name").EqualsIgnoreCase("busy_connections") Then
|
If rs.GetString("name").EqualsIgnoreCase("busy_connections") Then
|
||||||
columnExists = True
|
columnExists = True
|
||||||
Exit ' La columna ya existe, salimos del bucle.
|
Exit ' La columna ya existe, salimos del bucle.
|
||||||
End If
|
End If
|
||||||
Loop
|
Loop
|
||||||
rs.Close ' ¡Importante cerrar el ResultSet para liberar recursos!
|
rs.Close
|
||||||
|
|
||||||
If columnExists = False Then
|
If columnExists = False Then
|
||||||
Log("Añadiendo columna 'busy_connections' a query_logs.")
|
Log("Añadiendo columna 'busy_connections' a query_logs.")
|
||||||
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN busy_connections INTEGER DEFAULT 0")
|
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN busy_connections INTEGER DEFAULT 0")
|
||||||
@@ -341,85 +377,96 @@ Sub InitializeSQLiteDatabase
|
|||||||
|
|
||||||
' --- VERIFICAR Y AÑADIR handler_active_requests ---
|
' --- VERIFICAR Y AÑADIR handler_active_requests ---
|
||||||
columnExists = False
|
columnExists = False
|
||||||
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)") ' Ejecutamos PRAGMA nuevamente para esta columna.
|
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)")
|
||||||
|
|
||||||
Do While rs.NextRow
|
Do While rs.NextRow
|
||||||
If rs.GetString("name").EqualsIgnoreCase("handler_active_requests") Then
|
If rs.GetString("name").EqualsIgnoreCase("handler_active_requests") Then
|
||||||
columnExists = True
|
columnExists = True
|
||||||
Exit ' La columna ya existe, salimos del bucle.
|
Exit ' La columna ya existe, salimos del bucle.
|
||||||
End If
|
End If
|
||||||
Loop
|
Loop
|
||||||
rs.Close ' ¡Importante cerrar el ResultSet!
|
rs.Close
|
||||||
|
|
||||||
If columnExists = False Then
|
If columnExists = False Then
|
||||||
Log("Añadiendo columna 'handler_active_requests' a query_logs.")
|
Log("Añadiendo columna 'handler_active_requests' a query_logs.")
|
||||||
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0")
|
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
|
|
||||||
' >>> INICIO: Lógica de migración para 'errores' si la DB ya existía <<<
|
' >>> INICIO: Lógica de migración para 'errores' si la DB ya existía <<<
|
||||||
Log("Verificando y migrando tabla 'errores' si es necesario.")
|
Log("Verificando y migrando tabla 'errores' si es necesario.")
|
||||||
|
|
||||||
If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='errores'") = Null Then
|
If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='errores'") = Null Then
|
||||||
Log("Tabla 'errores' no encontrada, creándola.")
|
Log("Tabla 'errores' no encontrada, creándola.")
|
||||||
Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)"
|
Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)"
|
||||||
SQL1.ExecNonQuery(createErrorsTable)
|
SQL1.ExecNonQuery(createErrorsTable)
|
||||||
Else
|
Else
|
||||||
' Si la tabla ya existe, podrías añadir lógica para ALTER TABLE si se añaden nuevas columnas en el futuro.
|
|
||||||
' Por ahora, asumimos que la estructura inicial es suficiente.
|
|
||||||
Log("Tabla 'errores' ya existe.")
|
Log("Tabla 'errores' ya existe.")
|
||||||
End If
|
End If
|
||||||
' >>> FIN: Lógica de migración para 'errores' <<<
|
' >>> FIN: Lógica de migración para 'errores' <<<
|
||||||
|
|
||||||
End If
|
End If
|
||||||
' >>> FIN: Lógica de migración (ALTER TABLE) <<<
|
' >>> FIN: Lógica de migración (ALTER TABLE) <<<
|
||||||
|
|
||||||
End If
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' Subrutina para registrar las métricas de rendimiento de las queries en la tabla 'query_logs'.
|
' --- Subrutina para registrar las métricas de rendimiento de las queries en la tabla 'query_logs'. ---
|
||||||
|
' ¡MODIFICADA PARA USAR FILTRADO GRANULAR POR DBKEY!
|
||||||
Public Sub LogQueryPerformance(QueryName As String, DurationMs As Long, DbKey As String, ClientIp As String, HandlerActiveRequests As Int, PoolBusyConnections As Int)
|
Public Sub LogQueryPerformance(QueryName As String, DurationMs As Long, DbKey As String, ClientIp As String, HandlerActiveRequests As Int, PoolBusyConnections As Int)
|
||||||
Try
|
|
||||||
' Los valores de PoolBusyConnections y HandlerActiveRequests ya se reciben directamente del handler,
|
' Obtener el estado de logging para esta DBKey. Usar False si la DBKey no existe en el mapa.
|
||||||
' eliminando la necesidad de obtenerlos del conector en este punto.
|
Dim isEnabled As Boolean = SQLiteLoggingStatusByDB.GetDefault(DbKey, False)
|
||||||
|
|
||||||
' Insertamos los datos en la tabla query_logs de SQLite.
|
If isEnabled Then
|
||||||
SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip, busy_connections, handler_active_requests) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
Try
|
||||||
Array As Object(QueryName, DurationMs, DateTime.Now, DbKey, ClientIp, PoolBusyConnections, HandlerActiveRequests))
|
SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip, busy_connections, handler_active_requests) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
||||||
Catch
|
Array As Object(QueryName, DurationMs, DateTime.Now, DbKey, ClientIp, PoolBusyConnections, HandlerActiveRequests))
|
||||||
Log("Error al guardar log de query en SQLite (Main.LogQueryPerformance): " & LastException.Message)
|
Catch
|
||||||
End Try
|
Log("Error al guardar log de query en SQLite (Main.LogQueryPerformance): " & LastException.Message)
|
||||||
|
End Try
|
||||||
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' Subrutina para registrar errores y advertencias en la tabla 'errores'.
|
' --- Subrutina para registrar errores y advertencias en la tabla 'errores'. ---
|
||||||
' Type: "ERROR" o "ADVERTENCIA"
|
' ¡MODIFICADA PARA USAR FILTRADO GRANULAR POR DBKEY!
|
||||||
' Source: Módulo.Subrutina donde ocurrió el evento (ej. "DBHandlerJSON.Handle")
|
|
||||||
' Message: El mensaje descriptivo del error/advertencia.
|
|
||||||
' DBKey: La clave de la base de datos involucrada (ej. "DB1", "DB2"), Null si no aplica.
|
|
||||||
' CommandName: El nombre del comando SQL (ej. "select_user"), Null si no aplica.
|
|
||||||
' ClientIp: La dirección IP del cliente que originó la petición, Null si no aplica.
|
|
||||||
Public Sub LogServerError(Type0 As String, Source As String, Message As String, DBKey As String, CommandName As String, ClientIp As String)
|
Public Sub LogServerError(Type0 As String, Source As String, Message As String, DBKey As String, CommandName As String, ClientIp As String)
|
||||||
Try
|
|
||||||
SQL1.ExecNonQuery2("INSERT INTO errores (timestamp, type, source, message, db_key, command_name, client_ip) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
' Obtener el estado de logging para esta DBKey. Usar False si la DBKey es Null o no está en el mapa.
|
||||||
Array As Object(DateTime.Now, Type0, Source, Message, DBKey, CommandName, ClientIp))
|
Dim isEnabled As Boolean = SQLiteLoggingStatusByDB.GetDefault(DBKey, False)
|
||||||
Catch
|
|
||||||
Log("ERROR CRÍTICO: Fallo al guardar el log de error/advertencia en SQLite (Main.LogServerError): " & LastException.Message)
|
If isEnabled Then
|
||||||
' En este punto, no podemos hacer mucho más que loggear el fallo en la consola,
|
Try
|
||||||
' para evitar un bucle infinito de errores de logging.
|
SQL1.ExecNonQuery2("INSERT INTO errores (timestamp, type, source, message, db_key, command_name, client_ip) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
||||||
End Try
|
Array As Object(DateTime.Now, Type0, Source, Message, DBKey, CommandName, ClientIp))
|
||||||
|
Catch
|
||||||
|
Log("ERROR CRÍTICO: Fallo al guardar el log de error/advertencia en SQLite (Main.LogServerError): " & LastException.Message)
|
||||||
|
End Try
|
||||||
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' Subrutina de evento para el Timer 'timerLogs'.
|
' --- Subrutina de evento para el Timer 'timerLogs'. ---
|
||||||
' Se ejecuta periódicamente (cada 10 minutos) para limpiar la tabla de logs.
|
' El estado 'Enabled' del Timer ya está controlado por IsAnySQLiteLoggingEnabled en AppStart y Manager.
|
||||||
Sub TimerLogs_Tick
|
Sub TimerLogs_Tick
|
||||||
Try
|
Try
|
||||||
borraArribaDe15000Logs ' Llama a la función para limpiar los logs.
|
borraArribaDe15000Logs
|
||||||
Catch
|
Catch
|
||||||
Dim ErrorMsg As String = "ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message
|
Dim ErrorMsg As String = "ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message
|
||||||
Log(ErrorMsg)
|
Log(ErrorMsg)
|
||||||
LogServerError("ERROR", "Main.TimerLogs_Tick", ErrorMsg, Null, "log_cleanup", Null) ' <-- Nuevo Log
|
LogServerError("ERROR", "Main.TimerLogs_Tick", ErrorMsg, Null, "log_cleanup", Null)
|
||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' Borra los registros más antiguos de la tabla 'query_logs', manteniendo solo los 15,000 más recientes.
|
' --- Borra los registros más antiguos de la tabla 'query_logs' y hace VACUUM. ---
|
||||||
' Luego, optimiza el espacio de la base de datos SQLite con un 'vacuum'.
|
' ¡MODIFICADA PARA USAR FILTRADO GLOBAL!
|
||||||
Sub borraArribaDe15000Logs 'ignore
|
Sub borraArribaDe15000Logs 'ignore
|
||||||
Log("Recortando la tabla de 'query_logs', límite de 15,000 registros.")
|
|
||||||
SQL1.ExecNonQuery("DELETE FROM query_logs WHERE timestamp NOT in (SELECT timestamp FROM query_logs ORDER BY timestamp desc LIMIT 15000 )")
|
If IsAnySQLiteLoggingEnabled Then ' Solo ejecutar si al menos una DB requiere logs.
|
||||||
SQL1.ExecNonQuery("vacuum;") ' Optimiza el espacio de almacenamiento de la base de datos.
|
Log("Recortando la tabla de 'query_logs', límite de 15,000 registros.")
|
||||||
End Sub
|
SQL1.ExecNonQuery("DELETE FROM query_logs WHERE timestamp NOT in (SELECT timestamp FROM query_logs ORDER BY timestamp desc LIMIT 15000 )")
|
||||||
|
SQL1.ExecNonQuery("vacuum;")
|
||||||
|
Else
|
||||||
|
' Si IsAnySQLiteLoggingEnabled es False, el Timer no debería estar activo.
|
||||||
|
Log("AVISO: Tarea de limpieza de logs omitida. El logging global de SQLite está deshabilitado.")
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
@@ -43,6 +43,6 @@ ModuleClosedNodes6=
|
|||||||
ModuleClosedNodes7=
|
ModuleClosedNodes7=
|
||||||
ModuleClosedNodes8=
|
ModuleClosedNodes8=
|
||||||
ModuleClosedNodes9=
|
ModuleClosedNodes9=
|
||||||
NavigationStack=DBHandlerB4X,ExecuteBatch2,386,0,DBHandlerB4X,ExecuteBatch,449,0,DBHandlerB4X,ExecuteQuery,506,0,DBHandlerJSON,Handle,148,0,Main,Process_Globals,59,0,Manager,Handle,375,0,TestHandler,Handle,25,1,ChangePassHandler,Handle,17,0,Main,AppStart,157,0,Cambios,Process_Globals,13,6
|
NavigationStack=DBHandlerB4X,CleanupAndLog,198,0,DBHandlerJSON,CleanupAndLog,223,0,ParameterValidationUtils,ValidateAndAdjustParameters,45,0,Main,Process_Globals,53,0,Main,AppStart,186,0,Main,LogQueryPerformance,367,0,Main,LogServerError,384,6,Manager,Handle,164,6,Main,borraArribaDe15000Logs,412,0,Cambios,Process_Globals,25,6
|
||||||
SelectedBuild=0
|
SelectedBuild=0
|
||||||
VisibleModules=3,4,13,1,10,11,14,2
|
VisibleModules=3,4,13,1,10,11,14,2
|
||||||
|
|||||||
Reference in New Issue
Block a user