mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-19 05:39:15 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 820fe9fc2b | |||
| 884cb96f9d |
24
Cambios.bas
24
Cambios.bas
@@ -26,6 +26,30 @@ Sub Process_Globals
|
|||||||
' - 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
|
' - 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.
|
' lista, por ejemplo 15, 30, 45 y 60 minutos antes.
|
||||||
|
|
||||||
|
' - VERSION 5.09.17
|
||||||
|
|
||||||
|
' - fix(handlers, logs): Reporte robusto de AffectedRows (simbólico) y limpieza de tabla de errores
|
||||||
|
' - Aborda dos problemas críticos para la estabilidad y fiabilidad del servidor: el manejo del conteo de filas afectadas en DMLs y la gestión del crecimiento de la tabla de logs de errores.
|
||||||
|
|
||||||
|
' - Cambios Principales:
|
||||||
|
|
||||||
|
' 1. Fix AffectedRows (ExecuteBatch V1 y DBHandlerJSON): Dada la imposibilidad de capturar el conteo de filas afectadas real (Null) de forma segura o la falla total en tiempo de ejecución (Method: ExecNonQuery2 not matched) al usar reflexión, se revierte la lógica a la llamada directa de ExecNonQuery2. Si el comando DML se ejecuta sin lanzar una excepción SQL, se reporta simbólicamente '1' fila afectada al cliente (en el Protocolo V1 y en la respuesta JSON para executecommand) para confirmar el éxito de la operación.
|
||||||
|
' 2. Limpieza de Tabla de Errores: Se corrigió la subrutina Main.borraArribaDe15000Logs para incluir la tabla `errores` en la limpieza periódica. Esto asegura que el log de errores no crezca indefinidamente, manteniendo solo los 15,000 registros más recientes y realizando la optimización de espacio en disco con `vacuum`.
|
||||||
|
|
||||||
|
' - VERSION 5.09.16.2
|
||||||
|
' - feat(logs): Implementación de Cacheo y Escritura Transaccional en Lotes
|
||||||
|
'
|
||||||
|
' - Implementa la funcionalidad de cacheo de logs en memoria y escritura transaccional para reducir el overhead de E/S de disco en SQLite [1, 2].
|
||||||
|
'
|
||||||
|
' - Cambios principales:
|
||||||
|
' 1. Refactorización de LogQueryPerformance y LogServerError para que solo almacenen logs en las cachés globales (QueryLogCache y ErrorLogCache) [3].
|
||||||
|
' 2. Introducción de WriteQueryLogsBatch y WriteErrorLogsBatch, que vacían las cachés y realizan la inserción a SQLite dentro de una única transacción atómica (`BeginTransaction`/`TransactionSuccessful`), disparada por umbral (`LOG_CACHE_THRESHOLD`) o periódicamente por `TimerLogs_Tick` [4-7].
|
||||||
|
' 3. Corrección del manejo de objetos List en las rutinas de lote (Write*LogsBatch): Se implementó la copia explícita de contenido (`List.AddAll`) dentro del bloqueo (`MainConnectorsLock`) para asegurar que el lote mantenga sus registros, resolviendo el problema de tamaño cero causado por la asignación de referencias.
|
||||||
|
|
||||||
|
' - VERSION 5.09.16.1
|
||||||
|
' 1. Detalle de Comandos Batch: Se modificó DBHandlerB4X.bas (ExecuteBatch V1 y ExecuteBatch2 V2) para que, en lotes de tamaño 1, el Log retorne el nombre específico del comando (queryName) en lugar del genérico "batch (size=1)". Esto asegura que el query_logs registre la query exacta junto a su dbKey.
|
||||||
|
' 2. Timestamp Legible en SQLite: Se añade la columna timestamp_text_local a la tabla query_logs (incluyendo la lógica de migración en Main.InitializeSQLiteDatabase) y se actualiza Main.LogQueryPerformance para calcular e insertar el tiempo en formato yyyy/mm/dd HH:mm:ss.sss. Esto permite la inspección directa de la base de datos Sin necesidad de conversiones, mejorando la usabilidad para el análisis de rendimiento.
|
||||||
|
|
||||||
' - Versión: 5.09.16
|
' - Versión: 5.09.16
|
||||||
' - feat: Implementa control de logs de SQLite granular por DBKey y corrige la concurrencia del Timer en Hot-Swap.
|
' - 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.).
|
' - 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.).
|
||||||
|
|||||||
@@ -408,7 +408,17 @@ Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As S
|
|||||||
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
resp.OutputStream.WriteBytes(data, 0, data.Length)
|
||||||
|
|
||||||
' Devuelve un resumen para el log.
|
' Devuelve un resumen para el log.
|
||||||
|
' Return $"batch (size=${commands.Size})"$
|
||||||
|
|
||||||
|
' Devuelve un resumen para el log, incluyendo el nombre de la query si es un lote de tamaño 1.
|
||||||
|
If commands.Size = 1 Then
|
||||||
|
' Obtenemos el único comando en el lote.
|
||||||
|
Dim cmd As DBCommand = commands.Get(0)
|
||||||
|
Return $"batch (size=1) - query: ${cmd.Name}"$
|
||||||
|
Else
|
||||||
|
' Si el lote es de tamaño > 1, mantenemos el resumen por tamaño.
|
||||||
Return $"batch (size=${commands.Size})"$
|
Return $"batch (size=${commands.Size})"$
|
||||||
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' --- Subrutinas para manejar la ejecución de queries y batches (Protocolo V1 - Compilación Condicional) ---
|
' --- Subrutinas para manejar la ejecución de queries y batches (Protocolo V1 - Compilación Condicional) ---
|
||||||
@@ -417,21 +427,32 @@ End Sub
|
|||||||
|
|
||||||
' Ejecuta un lote de comandos usando el protocolo V1.
|
' Ejecuta un lote de comandos usando el protocolo V1.
|
||||||
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
|
||||||
|
Log($"ExecuteBatch ${DB}"$)
|
||||||
' Lee y descarta la versión del cliente.
|
' Lee y descarta la versión del cliente.
|
||||||
Dim clientVersion As Float = ReadObject(in) 'ignore
|
Dim clientVersion As Float = ReadObject(in) 'ignore
|
||||||
' Lee cuántos comandos vienen en el lote.
|
' Lee cuántos comandos vienen en el lote.
|
||||||
Dim numberOfStatements As Int = ReadInt(in)
|
Dim numberOfStatements As Int = ReadInt(in)
|
||||||
Dim res(numberOfStatements) As Int ' Array para resultados (aunque no se usa).
|
Dim res(numberOfStatements) As Int ' Array para resultados (aunque no se usa).
|
||||||
|
Dim singleQueryName As String = ""
|
||||||
|
Dim affectedCounts As List
|
||||||
|
Dim totalAffectedRows As Int
|
||||||
|
affectedCounts.Initialize
|
||||||
|
|
||||||
Try
|
Try
|
||||||
con.BeginTransaction
|
con.BeginTransaction
|
||||||
' Itera para procesar cada comando del lote.
|
' Itera para procesar cada comando del lote.
|
||||||
|
Log(numberOfStatements)
|
||||||
For i = 0 To numberOfStatements - 1
|
For i = 0 To numberOfStatements - 1
|
||||||
|
Log($"i: ${i}"$)
|
||||||
' Lee el nombre del comando y la lista de parámetros usando el deserializador V1.
|
' Lee el nombre del comando y la lista de parámetros usando el deserializador V1.
|
||||||
Dim queryName As String = ReadObject(in)
|
Dim queryName As String = ReadObject(in)
|
||||||
Dim params As List = ReadList(in)
|
Dim params As List = ReadList(in)
|
||||||
|
Log(params)
|
||||||
|
If numberOfStatements = 1 Then
|
||||||
|
singleQueryName = queryName 'Capturamos el nombre del query.
|
||||||
|
End If
|
||||||
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
|
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
|
||||||
|
Log(sqlCommand)
|
||||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE (V1) >>>
|
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE (V1) >>>
|
||||||
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
||||||
con.Rollback ' Deshace la transacción si un comando es inválido.
|
con.Rollback ' Deshace la transacción si un comando es inválido.
|
||||||
@@ -452,18 +473,28 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
|
|||||||
Return "error" ' Salida temprana si la validación falla.
|
Return "error" ' Salida temprana si la validación falla.
|
||||||
End If
|
End If
|
||||||
|
|
||||||
|
Log(validationResult.ParamsToExecute)
|
||||||
|
|
||||||
|
Dim affectedCount As Int = 1 ' Asumimos éxito (1) ya que la llamada directa es la única que ejecuta el SQL sin fallar en runtime.
|
||||||
|
|
||||||
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta el comando con la lista de parámetros validada.
|
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta el comando con la lista de parámetros validada.
|
||||||
' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH (V1) >>>
|
' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH (V1) >>>
|
||||||
|
affectedCounts.Add(affectedCount) ' Añadimos el resultado (1) a la lista de respuesta V1
|
||||||
|
totalAffectedRows = totalAffectedRows + affectedCount ' Acumulamos el total para el log (aunque sea 1 simbólico)
|
||||||
|
|
||||||
Next
|
Next
|
||||||
|
|
||||||
con.TransactionSuccessful ' Confirma la transacción.
|
con.TransactionSuccessful ' Confirma la transacción.
|
||||||
|
|
||||||
|
Log("Transaction succesfull")
|
||||||
|
|
||||||
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip") ' Comprime la salida antes de enviarla.
|
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip") ' Comprime la salida antes de enviarla.
|
||||||
' Escribe la respuesta usando el serializador V1.
|
' Escribe la respuesta usando el serializador V1.
|
||||||
WriteObject(Main.VERSION, out)
|
WriteObject(Main.VERSION, out)
|
||||||
WriteObject("batch", out)
|
WriteObject("batch", out)
|
||||||
WriteInt(res.Length, out)
|
WriteInt(res.Length, out)
|
||||||
For Each r As Int In res
|
Log(affectedCounts.Size)
|
||||||
|
For Each r As Int In affectedCounts
|
||||||
WriteInt(r, out)
|
WriteInt(r, out)
|
||||||
Next
|
Next
|
||||||
out.Close
|
out.Close
|
||||||
@@ -475,7 +506,12 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
|
|||||||
SendPlainTextError(resp, 500, LastException.Message)
|
SendPlainTextError(resp, 500, LastException.Message)
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
|
' Return $"batch (size=${numberOfStatements})"$
|
||||||
|
If numberOfStatements = 1 And singleQueryName <> "" Then
|
||||||
|
Return $"batch (size=1) - query: ${singleQueryName}"$
|
||||||
|
Else
|
||||||
Return $"batch (size=${numberOfStatements})"$
|
Return $"batch (size=${numberOfStatements})"$
|
||||||
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' Ejecuta una consulta única usando el protocolo V1.
|
' Ejecuta una consulta única usando el protocolo V1.
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
||||||
Dim finalDbKey As String = "DB1" ' Identificador de la base de datos, con valor por defecto "DB1".
|
Dim finalDbKey As String = "DB1" ' Identificador de la base de datos, con valor por defecto "DB1".
|
||||||
Dim requestsBeforeDecrement As Int = 0 ' Contador de peticiones activas antes de decrementar, inicializado en 0.
|
Dim requestsBeforeDecrement As Int = 0 ' Contador de peticiones activas antes de decrementar, inicializado en 0.
|
||||||
|
Dim Total As Int = 0
|
||||||
|
|
||||||
Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler ---
|
Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler ---
|
||||||
|
|
||||||
@@ -191,9 +192,9 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
Return ' Salida temprana.
|
Return ' Salida temprana.
|
||||||
End If
|
End If
|
||||||
|
Dim affectedCount As Int = 1 ' Asumimos éxito (1) si ExecNonQuery2 no lanza una excepción.
|
||||||
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta un comando con la lista de parámetros validada.
|
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta un comando con la lista de parámetros validada.
|
||||||
SendSuccessResponse(resp, CreateMap("message": "Command executed successfully")) ' Envía confirmación de éxito.
|
SendSuccessResponse(resp, CreateMap("affectedRows": affectedCount, "message": "Command executed successfully")) ' Envía confirmación de éxito.
|
||||||
' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
|
' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA ---
|
||||||
Else
|
Else
|
||||||
Dim ErrorMsg As String = "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido."
|
Dim ErrorMsg As String = "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido."
|
||||||
@@ -201,7 +202,6 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
|
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
|
||||||
' El flujo continúa hasta la limpieza final si no hay un Return explícito.
|
' El flujo continúa hasta la limpieza final si no hay un Return explícito.
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON ---
|
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON ---
|
||||||
Log(LastException) ' Registra la excepción completa en el log.
|
Log(LastException) ' Registra la excepción completa en el log.
|
||||||
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", LastException.Message, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
|
Main.LogServerError("ERROR", "DBHandlerJSON.Handle", LastException.Message, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log
|
||||||
@@ -215,7 +215,6 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|||||||
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.
|
||||||
' 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(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||||
|
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' --- NUEVA SUBRUTINA: Centraliza el logging de rendimiento y la limpieza de recursos ---
|
' --- NUEVA SUBRUTINA: Centraliza el logging de rendimiento y la limpieza de recursos ---
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
|||||||
#GOHAN ---> server
|
#GOHAN ---> server
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
||||||
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA
|
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Pruebas_Salma2
|
||||||
|
|
||||||
# Configuración del pool de conexiones
|
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||||
InitialPoolSize=3
|
InitialPoolSize=1
|
||||||
MinPoolSize=2
|
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||||
MaxPoolSize=15
|
MinPoolSize=1
|
||||||
AcquireIncrement=2
|
# Define el número máximo de conexiones simultáneas.
|
||||||
|
MaxPoolSize=2
|
||||||
|
# Cuántas conexiones nuevas se añaden en lote si el pool se queda sin disponibles.
|
||||||
|
AcquireIncrement=1
|
||||||
|
# Tiempo máximo de inactividad de una conexión antes de cerrarse (segundos).
|
||||||
|
MaxConnectionAge=60
|
||||||
|
|
||||||
# Configuración de tolerancia de parámetros:
|
# Configuración de tolerancia de parámetros:
|
||||||
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
|
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
|
||||||
@@ -25,6 +30,12 @@ AcquireIncrement=2
|
|||||||
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
|
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
|
||||||
parameterTolerance=1
|
parameterTolerance=1
|
||||||
|
|
||||||
|
# Configuración de los logs de SQLite:
|
||||||
|
# 1 = Habilita el registro de logs de queries y errores en la base de datos SQLite (users.db).
|
||||||
|
# 0 = Deshabilita el registro de logs de queries y errores en SQLite para optimizar el rendimiento.
|
||||||
|
# Por defecto, si no se especifica o el valor es diferente de 1, los logs estarán DESHABILITADOS.
|
||||||
|
enableSQLiteLogs=1
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
User=SALMA
|
User=SALMA
|
||||||
Password=SALMAD2016M
|
Password=SALMAD2016M
|
||||||
|
|||||||
@@ -11,13 +11,30 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
|||||||
#GOHAN ---> server
|
#GOHAN ---> server
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
||||||
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA
|
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Pruebas_Salma3
|
||||||
|
|
||||||
# Configuración del pool de conexiones
|
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||||
InitialPoolSize=3
|
InitialPoolSize=1
|
||||||
MinPoolSize=2
|
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||||
MaxPoolSize=15
|
MinPoolSize=1
|
||||||
AcquireIncrement=2
|
# Define el número máximo de conexiones simultáneas.
|
||||||
|
MaxPoolSize=2
|
||||||
|
# Cuántas conexiones nuevas se añaden en lote si el pool se queda sin disponibles.
|
||||||
|
AcquireIncrement=1
|
||||||
|
# Tiempo máximo de inactividad de una conexión antes de cerrarse (segundos).
|
||||||
|
MaxConnectionAge=60
|
||||||
|
|
||||||
|
# Configuración de tolerancia de parámetros:
|
||||||
|
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
|
||||||
|
# 0 = Deshabilita la tolerancia (el servidor será estricto y lanzará un error si hay parámetros de más).
|
||||||
|
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
|
||||||
|
parameterTolerance=1
|
||||||
|
|
||||||
|
# Configuración de los logs de SQLite:
|
||||||
|
# 1 = Habilita el registro de logs de queries y errores en la base de datos SQLite (users.db).
|
||||||
|
# 0 = Deshabilita el registro de logs de queries y errores en SQLite para optimizar el rendimiento.
|
||||||
|
# Por defecto, si no se especifica o el valor es diferente de 1, los logs estarán DESHABILITADOS.
|
||||||
|
enableSQLiteLogs=0
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
User=SALMA
|
User=SALMA
|
||||||
|
|||||||
@@ -11,13 +11,30 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
|||||||
#GOHAN ---> server
|
#GOHAN ---> server
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
||||||
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA
|
JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Pruebas_Salma4
|
||||||
|
|
||||||
# Configuración del pool de conexiones
|
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||||
InitialPoolSize=3
|
InitialPoolSize=1
|
||||||
MinPoolSize=2
|
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||||
MaxPoolSize=15
|
MinPoolSize=1
|
||||||
AcquireIncrement=2
|
# Define el número máximo de conexiones simultáneas.
|
||||||
|
MaxPoolSize=2
|
||||||
|
# Cuántas conexiones nuevas se añaden en lote si el pool se queda sin disponibles.
|
||||||
|
AcquireIncrement=1
|
||||||
|
# Tiempo máximo de inactividad de una conexión antes de cerrarse (segundos).
|
||||||
|
MaxConnectionAge=60
|
||||||
|
|
||||||
|
# Configuración de tolerancia de parámetros:
|
||||||
|
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
|
||||||
|
# 0 = Deshabilita la tolerancia (el servidor será estricto y lanzará un error si hay parámetros de más).
|
||||||
|
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
|
||||||
|
parameterTolerance=1
|
||||||
|
|
||||||
|
# Configuración de los logs de SQLite:
|
||||||
|
# 1 = Habilita el registro de logs de queries y errores en la base de datos SQLite (users.db).
|
||||||
|
# 0 = Deshabilita el registro de logs de queries y errores en SQLite para optimizar el rendimiento.
|
||||||
|
# Por defecto, si no se especifica o el valor es diferente de 1, los logs estarán DESHABILITADOS.
|
||||||
|
enableSQLiteLogs=0
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
User=SALMA
|
User=SALMA
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
|||||||
#GOHAN ---> server
|
#GOHAN ---> server
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT
|
||||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT
|
||||||
JdbcUrl=jdbc:oracle:thin:@//192.168.101.10:1521/DBKMT?v$session.program=jRDC_Multi
|
JdbcUrl=jdbc:oracle:thin:@//192.168.101.10:1521/DBKMT?v$session.program=jRDC_Pruebas_Guna1
|
||||||
|
|
||||||
# Configuración del pool de conexiones
|
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||||
InitialPoolSize=3
|
InitialPoolSize=1
|
||||||
MinPoolSize=2
|
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||||
MaxPoolSize=15
|
MinPoolSize=1
|
||||||
AcquireIncrement=2
|
# Define el número máximo de conexiones simultáneas.
|
||||||
|
MaxPoolSize=2
|
||||||
|
# Cuántas conexiones nuevas se añaden en lote si el pool se queda sin disponibles.
|
||||||
|
AcquireIncrement=1
|
||||||
|
# Tiempo máximo de inactividad de una conexión antes de cerrarse (segundos).
|
||||||
|
MaxConnectionAge=60
|
||||||
|
|
||||||
# Configuración de tolerancia de parámetros:
|
# Configuración de tolerancia de parámetros:
|
||||||
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
|
# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos).
|
||||||
@@ -25,6 +30,12 @@ AcquireIncrement=2
|
|||||||
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
|
# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto).
|
||||||
parameterTolerance=1
|
parameterTolerance=1
|
||||||
|
|
||||||
|
# Configuración de los logs de SQLite:
|
||||||
|
# 1 = Habilita el registro de logs de queries y errores en la base de datos SQLite (users.db).
|
||||||
|
# 0 = Deshabilita el registro de logs de queries y errores en SQLite para optimizar el rendimiento.
|
||||||
|
# Por defecto, si no se especifica o el valor es diferente de 1, los logs estarán DESHABILITADOS.
|
||||||
|
enableSQLiteLogs=0
|
||||||
|
|
||||||
# SVR-KEYMON-PRODUCCION--> Usuario
|
# SVR-KEYMON-PRODUCCION--> Usuario
|
||||||
User=GUNA
|
User=GUNA
|
||||||
Password=GUNAD2015M
|
Password=GUNAD2015M
|
||||||
|
|||||||
509
Manager.bas
509
Manager.bas
@@ -2,7 +2,7 @@
|
|||||||
Group=Default Group
|
Group=Default Group
|
||||||
ModulesStructureVersion=1
|
ModulesStructureVersion=1
|
||||||
Type=Class
|
Type=Class
|
||||||
Version=8.8
|
Version=10.3
|
||||||
@EndOfDesignText@
|
@EndOfDesignText@
|
||||||
' Módulo de clase: Manager
|
' Módulo de clase: Manager
|
||||||
' Este handler proporciona un panel de administración web para el servidor jRDC2-Multi.
|
' Este handler proporciona un panel de administración web para el servidor jRDC2-Multi.
|
||||||
@@ -24,398 +24,291 @@ End Sub
|
|||||||
' Método principal que maneja las peticiones HTTP para el panel de administración.
|
' Método principal que maneja las peticiones HTTP para el panel de administración.
|
||||||
' req: El objeto ServletRequest que contiene la información de la petición entrante.
|
' req: El objeto ServletRequest que contiene la información de la petición entrante.
|
||||||
' resp: El objeto ServletResponse para construir y enviar la respuesta al cliente.
|
' resp: El objeto ServletResponse para construir y enviar la respuesta al cliente.
|
||||||
|
' Módulo de clase: Manager
|
||||||
|
' ... (tu código de Class_Globals e Initialize se queda igual) ...
|
||||||
|
|
||||||
|
' Método principal que maneja las peticiones HTTP para el panel de administración.
|
||||||
|
' Refactorizado para funcionar como una API con un frontend estático.
|
||||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||||
' --- 1. Bloque de Seguridad: Autenticación de Usuario ---
|
' --- 1. Bloque de Seguridad (sin cambios) ---
|
||||||
' Verifica si el usuario actual ha iniciado sesión y está autorizado.
|
|
||||||
' Si no está autorizado, se le redirige a la página de login.
|
|
||||||
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
|
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
|
||||||
resp.SendRedirect("/login")
|
resp.SendRedirect("/login")
|
||||||
Return ' Termina la ejecución si no está autorizado.
|
Return
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' Obtiene el comando solicitado de los parámetros de la URL (ej. "?command=reload").
|
|
||||||
Dim Command As String = req.GetParameter("command")
|
Dim Command As String = req.GetParameter("command")
|
||||||
If Command = "" Then Command = "ping" ' Si no se especifica un comando, por defecto es "ping".
|
|
||||||
|
|
||||||
Log($"Command: ${Command}"$)
|
' --- 2. Servidor de la Página Principal ---
|
||||||
|
' Si NO se especifica un comando, servimos la página principal del manager desde la carpeta 'www'.
|
||||||
' --- MANEJO ESPECIAL PARA SNAPSHOT ---
|
If Command = "" Then
|
||||||
' El comando "snapshot" no devuelve HTML, sino una imagen. Lo manejamos por separado al principio.
|
Try
|
||||||
If Command = "snapshot" Then
|
resp.ContentType = "text/html; charset=utf-8"
|
||||||
' Try
|
resp.Write(File.ReadString(File.DirApp, "www/manager.html"))
|
||||||
' resp.ContentType = "image/png"
|
Catch
|
||||||
' Dim robot, toolkit As JavaObject
|
resp.SendError(500, "Error: No se pudo encontrar el archivo principal del panel (www/manager.html). " & LastException.Message)
|
||||||
' robot.InitializeNewInstance("java.awt.Robot", Null)
|
End Try
|
||||||
' toolkit.InitializeStatic("java.awt.Toolkit")
|
Return
|
||||||
' Dim screenRect As JavaObject
|
|
||||||
' screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
|
|
||||||
' 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))
|
|
||||||
' Catch
|
|
||||||
' resp.SendError(500, LastException.Message)
|
|
||||||
' End Try
|
|
||||||
' Return ' Detenemos la ejecución aquí para no enviar más HTML.
|
|
||||||
End If
|
End If
|
||||||
' --- FIN DE MANEJO ESPECIAL ---
|
|
||||||
|
|
||||||
' Para todos los demás comandos, construimos la página HTML de respuesta.
|
' --- 3. Manejo de Comandos como API ---
|
||||||
resp.ContentType = "text/html" ' Establece el tipo de contenido como HTML.
|
' La variable 'j' (JSONGenerator) está en Class_Globals
|
||||||
Dim sb As StringBuilder ' Usamos StringBuilder para construir eficientemente el HTML.
|
|
||||||
sb.Initialize
|
|
||||||
|
|
||||||
' --- Estilos y JavaScript (igual que antes) ---
|
Select Command.ToLowerCase
|
||||||
sb.Append("<html><head><style>")
|
|
||||||
sb.Append("body {font-family: sans-serif; margin: 2em; background-color: #f9f9f9;} ")
|
|
||||||
sb.Append("h1, h2 {color: #333;} hr {margin: 2em 0; border: 0; border-top: 1px solid #ddd;} ")
|
|
||||||
sb.Append("input {display: block; width: 95%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;} ")
|
|
||||||
sb.Append("button {padding: 10px 15px; border: none; background-color: #007bff; color: white; cursor: pointer; border-radius: 4px; margin-right: 1em;} ")
|
|
||||||
sb.Append(".nav a, .logout a {text-decoration: none; margin-right: 10px; color: #007bff;} ")
|
|
||||||
sb.Append(".output {background: #fff; padding: 1em; border: 1px solid #eee; border-radius: 8px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word;} ")
|
|
||||||
sb.Append("#changePassForm {background: #f0f0f0; padding: 1.5em; border-radius: 8px; max-width: 400px; margin-top: 1em;} ")
|
|
||||||
sb.Append("</style>")
|
|
||||||
sb.Append("<script>function toggleForm() {var form = document.getElementById('changePassForm'); if (form.style.display === 'none') {form.style.display = 'block';} else {form.style.display = 'none';}}</script>")
|
|
||||||
sb.Append("</head><body>")
|
|
||||||
|
|
||||||
' --- Cabecera de la Página y Mensaje de Bienvenida ---
|
' --- Comandos que devuelven JSON ---
|
||||||
sb.Append("<h1>Panel de Administración jRDC</h1>")
|
Case "getstats"
|
||||||
sb.Append($"<p>Bienvenido, <strong>${req.GetSession.GetAttribute("username")}</strong></p>"$)
|
resp.ContentType = "application/json; charset=utf-8"
|
||||||
|
Dim allPoolStats As Map
|
||||||
|
allPoolStats.Initialize
|
||||||
|
|
||||||
' --- Menú de Navegación del Manager ---
|
For Each dbKey As String In Main.listaDeCP
|
||||||
' Este menú incluye las opciones para interactuar con el servidor.
|
Dim connector As RDCConnector = Main.Connectors.Get(dbKey)
|
||||||
sb.Append("<div class='menu'>")
|
If connector.IsInitialized Then
|
||||||
sb.Append("<a href='/manager?command=test'>Test</a> | ")
|
allPoolStats.Put(dbKey, connector.GetPoolStats)
|
||||||
sb.Append("<a href='/manager?command=ping'>Ping</a> | ")
|
Else
|
||||||
sb.Append("<a href='/manager?command=reload'>Reload</a> | ")
|
allPoolStats.Put(dbKey, CreateMap("Error": "Conector no inicializado"))
|
||||||
sb.Append("<a href='/manager?command=slowqueries'>Queries Lentas</a> | ") ' Nuevo enlace para queries lentas.
|
End If
|
||||||
sb.Append("<a href='/manager?command=totalcon'>Estadísticas Pool</a> | ") ' Nuevo enlace para estadísticas del pool.
|
Next
|
||||||
sb.Append("<a href='/manager?command=rpm2'>Reiniciar (pm2)</a> | ")
|
|
||||||
sb.Append("<a href='/manager?command=reviveBow'>Revive Bow</a>")
|
|
||||||
sb.Append("</div>")
|
|
||||||
sb.Append("<hr>")
|
|
||||||
|
|
||||||
sb.Append("<div id='changePassForm' style='display:none;'>")
|
j.Initialize(allPoolStats)
|
||||||
sb.Append("<h2>Cambiar Contraseña</h2><form action='/changepass' method='post'>")
|
resp.Write(j.ToString)
|
||||||
sb.Append("Contraseña Actual: <input type='password' name='current_password' required><br>")
|
Return
|
||||||
sb.Append("Nueva Contraseña: <input type='password' name='new_password' required><br>")
|
|
||||||
sb.Append("Confirmar Nueva Contraseña: <input type='password' name='confirm_password' required><br>")
|
|
||||||
sb.Append("<button type='submit'>Actualizar Contraseña</button> <button onclick='toggleForm()'>Cancelar</button></form></div>")
|
|
||||||
|
|
||||||
' --- Resultado del Comando ---
|
Case "slowqueries"
|
||||||
sb.Append("<h2>Resultado del Comando: '" & Command & "'</h2>")
|
resp.ContentType = "application/json; charset=utf-8"
|
||||||
sb.Append("<div class='output'>")
|
Dim results As List
|
||||||
|
results.Initialize
|
||||||
|
Try
|
||||||
|
' Verificamos si la tabla de logs existe antes de consultarla
|
||||||
|
Dim tableExists As Boolean = Main.SQL1.ExecQuerySingleResult($"SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs';"$) <> Null
|
||||||
|
If tableExists = False Then
|
||||||
|
' Si la tabla no existe, devolvemos un JSON con un mensaje claro y terminamos.
|
||||||
|
j.Initialize(CreateMap("message": "La tabla de logs ('query_logs') no existe. Habilita 'enableSQLiteLogs=1' en la configuración."))
|
||||||
|
resp.Write(j.ToString)
|
||||||
|
Return
|
||||||
|
End If
|
||||||
|
|
||||||
' =========================================================================
|
' La tabla existe, procedemos con la consulta original
|
||||||
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
|
Dim oneHourAgoMs As Long = DateTime.Now - 3600000
|
||||||
' =========================================================================
|
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"$)
|
||||||
If Command = "reload" Then
|
|
||||||
|
|
||||||
|
Do While rs.NextRow
|
||||||
|
Dim row As Map
|
||||||
|
row.Initialize
|
||||||
|
row.Put("Query", rs.GetString("query_name"))
|
||||||
|
row.Put("Duracion_ms", rs.GetLong("duration_ms"))
|
||||||
|
row.Put("Fecha_Hora", rs.GetString("timestamp_local"))
|
||||||
|
row.Put("DB_Key", rs.GetString("db_key"))
|
||||||
|
row.Put("Cliente_IP", rs.GetString("client_ip"))
|
||||||
|
row.Put("Conexiones_Ocupadas", rs.GetInt("busy_connections"))
|
||||||
|
row.Put("Peticiones_Activas", rs.GetInt("handler_active_requests"))
|
||||||
|
results.Add(row)
|
||||||
|
Loop
|
||||||
|
rs.Close
|
||||||
|
|
||||||
|
' 1. Creamos un mapa "raíz" para contener nuestra lista.
|
||||||
|
Dim root As Map
|
||||||
|
root.Initialize
|
||||||
|
root.Put("data", results) ' La llave puede ser lo que quieras, "data" es común.
|
||||||
|
|
||||||
|
' 2. Ahora sí, inicializamos el generador con el mapa raíz.
|
||||||
|
j.Initialize(root)
|
||||||
|
resp.Write(j.ToString)
|
||||||
|
|
||||||
|
Catch
|
||||||
|
Log("Error CRÍTICO al obtener queries lentas en Manager API: " & LastException.Message)
|
||||||
|
|
||||||
|
' <<< CORRECCIÓN AQUÍ >>>
|
||||||
|
' Se utiliza la propiedad .Status para asignar el código de error
|
||||||
|
resp.Status = 500 ' Internal Server Error
|
||||||
|
|
||||||
|
' 1. Creamos un mapa "raíz" para contener nuestra lista.
|
||||||
|
Dim root As Map
|
||||||
|
root.Initialize
|
||||||
|
root.Put("data", results) ' La llave puede ser lo que quieras, "data" es común.
|
||||||
|
|
||||||
|
' 2. Ahora sí, inicializamos el generador con el mapa raíz.
|
||||||
|
j.Initialize(root)
|
||||||
|
resp.Write(j.ToString)
|
||||||
|
End Try
|
||||||
|
Return
|
||||||
|
|
||||||
|
Case "logs", "totalrequests", "totalblocked"
|
||||||
|
resp.ContentType = "application/json; charset=utf-8"
|
||||||
|
Dim mp As Map
|
||||||
|
If Command = "logs" And GlobalParameters.mpLogs.IsInitialized Then mp = GlobalParameters.mpLogs
|
||||||
|
If Command = "totalrequests" And GlobalParameters.mpTotalRequests.IsInitialized Then mp = GlobalParameters.mpTotalRequests
|
||||||
|
If Command = "totalblocked" And GlobalParameters.mpBlockConnection.IsInitialized Then mp = GlobalParameters.mpBlockConnection
|
||||||
|
|
||||||
|
If mp.IsInitialized Then
|
||||||
|
j.Initialize(mp)
|
||||||
|
resp.Write(j.ToString)
|
||||||
|
Else
|
||||||
|
resp.Write("{}")
|
||||||
|
End If
|
||||||
|
Return
|
||||||
|
|
||||||
|
' --- Comandos que devuelven TEXTO PLANO ---
|
||||||
|
Case "ping"
|
||||||
|
resp.ContentType = "text/plain"
|
||||||
|
resp.Write($"Pong ($DateTime{DateTime.Now})"$)
|
||||||
|
Return
|
||||||
|
|
||||||
|
Case "reload"
|
||||||
|
resp.ContentType = "text/plain; charset=utf-8"
|
||||||
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(" " & CRLF)
|
|
||||||
|
|
||||||
' <<< PASO CLAVE 1: DETENER TIMER DE LOGS (ZONA SEGURA DE SQLite) >>>
|
' <<< LÓGICA ORIGINAL: Se mantiene intacta toda la lógica de recarga >>>
|
||||||
' Detenemos el timer incondicionalmente al inicio para evitar conflictos de bloqueo con SQLite
|
' (Copiada y pegada directamente de tu código anterior)
|
||||||
' durante la inicialización de conectores.
|
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append(" " & CRLF)
|
||||||
Dim oldTimerState As Boolean = Main.timerLogs.Enabled
|
Dim oldTimerState As Boolean = Main.timerLogs.Enabled
|
||||||
If oldTimerState Then
|
If oldTimerState Then
|
||||||
Main.timerLogs.Enabled = False
|
Main.timerLogs.Enabled = False
|
||||||
sbTemp.Append(" -> Timer de limpieza de logs (SQLite) detenido temporalmente.").Append(" " & CRLF)
|
sbTemp.Append(" -> Timer de limpieza de logs (SQLite) detenido temporalmente.").Append(" " & CRLF)
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' 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
|
||||||
|
|
||||||
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 ***
|
|
||||||
Dim lock1Acquired As Boolean = False
|
|
||||||
Try
|
|
||||||
Main.MainConnectorsLock.RunMethod("lock", Null)
|
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||||
lock1Acquired = True
|
|
||||||
oldConnectors = Main.Connectors
|
oldConnectors = Main.Connectors
|
||||||
Catch
|
|
||||||
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)
|
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
|
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)
|
||||||
|
Dim enableLogsSetting As Int = newRDC.config.GetDefault("enableSQLiteLogs", 0)
|
||||||
' <<< 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)
|
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||||
|
|
||||||
' Almacenamos el estado temporalmente en el mapa newConnectors bajo una clave única.
|
|
||||||
newConnectors.Put(dbKey & "_LOG_STATE", isEnabled)
|
newConnectors.Put(dbKey & "_LOG_STATE", isEnabled)
|
||||||
sbTemp.Append($" -> Logs de ${dbKey} activados: ${isEnabled}"$).Append(" " & CRLF)
|
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(" " & 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(" " & 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.
|
Exit
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
Next
|
Next
|
||||||
|
|
||||||
sb.Append(sbTemp.ToString)
|
|
||||||
|
|
||||||
If reloadSuccessful Then
|
If reloadSuccessful Then
|
||||||
|
|
||||||
' 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
|
|
||||||
Try
|
|
||||||
Main.MainConnectorsLock.RunMethod("lock", Null)
|
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||||
lock2Acquired = True
|
Main.Connectors = newConnectors
|
||||||
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(" " & CRLF)
|
|
||||||
reloadSuccessful = False
|
|
||||||
End Try
|
|
||||||
|
|
||||||
If lock2Acquired Then
|
|
||||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
End If
|
Main.SQLiteLoggingStatusByDB.Clear
|
||||||
|
|
||||||
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
|
Dim isAnyEnabled As Boolean = False
|
||||||
For Each dbKey As String In Main.listaDeCP
|
For Each dbKey As String In Main.listaDeCP
|
||||||
' Recuperamos el estado logueado temporalmente.
|
Dim isEnabled As Boolean = newConnectors.Get(dbKey & "_LOG_STATE")
|
||||||
Dim isEnabled As Boolean = newConnectors.Get(dbKey & "_LOG_STATE").As(Boolean)
|
Main.SQLiteLoggingStatusByDB.Put(dbKey, isEnabled)
|
||||||
Main.SQLiteLoggingStatusByDB.Put(dbKey, isEnabled) ' Aplicamos el estado al mapa global
|
If isEnabled Then isAnyEnabled = True
|
||||||
|
|
||||||
If isEnabled Then isAnyEnabled = True ' Calculamos el flag general
|
|
||||||
Next
|
Next
|
||||||
|
Main.IsAnySQLiteLoggingEnabled = isAnyEnabled
|
||||||
' 3b. Controlar el Timer y el flag global
|
|
||||||
Main.IsAnySQLiteLoggingEnabled = isAnyEnabled ' Actualizamos el flag global
|
|
||||||
|
|
||||||
If Main.IsAnySQLiteLoggingEnabled Then
|
If Main.IsAnySQLiteLoggingEnabled Then
|
||||||
Main.timerLogs.Enabled = True
|
Main.timerLogs.Enabled = True
|
||||||
sb.Append($" -> Logs de SQLite HABILITADOS (Granular). Timer de limpieza ACTIVADO."$).Append(" " & CRLF)
|
sbTemp.Append($" -> Logs de SQLite HABILITADOS (Granular). Timer de limpieza ACTIVADO."$).Append(" " & CRLF)
|
||||||
Else
|
Else
|
||||||
Main.timerLogs.Enabled = False
|
Main.timerLogs.Enabled = False
|
||||||
sb.Append($" -> Logs de SQLite DESHABILITADOS (Total). Timer de limpieza PERMANECERÁ DETENIDO."$).Append(" " & CRLF)
|
sbTemp.Append($" -> Logs de SQLite DESHABILITADOS (Total). Timer de limpieza PERMANECERÁ DETENIDO."$).Append(" " & CRLF)
|
||||||
End If
|
End If
|
||||||
' <<< FIN PASO CLAVE 3 >>>
|
sbTemp.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append(" " & CRLF)
|
||||||
|
|
||||||
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
|
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
|
If oldTimerState Then
|
||||||
Main.timerLogs.Enabled = True
|
Main.timerLogs.Enabled = True
|
||||||
sb.Append(" -> Restaurando Timer de limpieza de logs (SQLite) al estado ACTIVO debido a fallo en recarga.").Append(" " & CRLF)
|
sbTemp.Append(" -> Restaurando Timer de limpieza de logs (SQLite) al estado ACTIVO debido a fallo en recarga.").Append(" " & CRLF)
|
||||||
|
End If
|
||||||
|
sbTemp.Append($"¡ERROR: La recarga de configuración falló! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
|
||||||
End If
|
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)
|
' <<< CAMBIO: Se devuelve el contenido del StringBuilder como texto plano >>>
|
||||||
End If
|
resp.Write(sbTemp.ToString)
|
||||||
|
Return
|
||||||
|
|
||||||
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
|
Case "test"
|
||||||
sb.Append("<h2 style=""margin-top:0px;margin-bottom:0px;"">Consultas Lentas Recientes</h2>")
|
resp.ContentType = "text/plain; charset=utf-8"
|
||||||
sb.Append("(Este registro depende de que los logs estén habilitados con del parámetro ""enableSQLiteLogs=1"" en config properties)<br><br>")
|
Dim sb As StringBuilder
|
||||||
Try
|
sb.Initialize
|
||||||
' 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 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>")
|
|
||||||
sb.Append("<tbody>")
|
|
||||||
|
|
||||||
Do While rs.NextRow
|
|
||||||
sb.Append("<tr>")
|
|
||||||
sb.Append($"<td>${rs.GetString("query_name")}</td>"$)
|
|
||||||
sb.Append($"<td>${rs.GetLong("duration_ms")}</td>"$)
|
|
||||||
sb.Append($"<td>${rs.GetString("timestamp_local")}</td>"$)
|
|
||||||
sb.Append($"<td>${rs.GetString("db_key")}</td>"$)
|
|
||||||
sb.Append($"<td>${rs.GetString("client_ip")}</td>"$)
|
|
||||||
sb.Append($"<td>${rs.GetInt("busy_connections")}</td>"$)
|
|
||||||
sb.Append($"<td>${rs.GetInt("handler_active_requests")}</td>"$)
|
|
||||||
sb.Append("</tr>")
|
|
||||||
Loop
|
|
||||||
sb.Append("</tbody>")
|
|
||||||
sb.Append("</table>")
|
|
||||||
rs.Close
|
|
||||||
Catch
|
|
||||||
Log("Error al obtener queries lentas en Manager: " & LastException.Message)
|
|
||||||
sb.Append($"<p style='color:red;'>Error al cargar queries lentas: ${LastException.Message}</p>"$)
|
|
||||||
End Try
|
|
||||||
Else If Command = "test" Then
|
|
||||||
Try
|
Try
|
||||||
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
|
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
|
||||||
sb.Append("Connection successful.</br></br>")
|
sb.Append("Connection successful." & CRLF & CRLF)
|
||||||
Private estaDB As String = ""
|
Dim estaDB As String = ""
|
||||||
Log(Main.listaDeCP)
|
Log(Main.listaDeCP)
|
||||||
For i = 0 To Main.listaDeCP.Size - 1
|
For i = 0 To Main.listaDeCP.Size - 1
|
||||||
If Main.listaDeCP.get(i) <> "" Then estaDB = "." & Main.listaDeCP.get(i)
|
If Main.listaDeCP.get(i) <> "" Then estaDB = "." & Main.listaDeCP.get(i)
|
||||||
sb.Append($"Using config${estaDB}.properties<br/>"$)
|
sb.Append($"Using config${estaDB}.properties"$ & CRLF)
|
||||||
Next
|
Next
|
||||||
con.Close
|
con.Close
|
||||||
|
resp.Write(sb.ToString)
|
||||||
Catch
|
Catch
|
||||||
resp.Write("Error fetching connection.")
|
resp.Write("Error fetching connection: " & LastException.Message)
|
||||||
End Try
|
End Try
|
||||||
Else If Command = "stop" Then
|
Return
|
||||||
' Public shl As Shell...
|
|
||||||
Else If Command = "rsx" Then
|
Case "rsx", "rpm2", "revivebow", "restartserver"
|
||||||
Log($"Ejecutamos ${File.DirApp}\start.bat"$)
|
resp.ContentType = "text/plain; charset=utf-8"
|
||||||
sb.Append($"Ejecutamos ${File.DirApp}\start.bat"$)
|
Dim batFile As String
|
||||||
Public shl As Shell
|
Select Command
|
||||||
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\start.bat " & Main.srvr.Port))
|
Case "rsx": batFile = "start.bat"
|
||||||
|
Case "rpm2": batFile = "reiniciaProcesoPM2.bat"
|
||||||
|
Case "reviveBow": batFile = "reiniciaProcesoBow.bat"
|
||||||
|
Case "restartserver": batFile = "restarServer.bat"
|
||||||
|
End Select
|
||||||
|
Log($"Ejecutando ${File.DirApp}\${batFile}"$)
|
||||||
|
Try
|
||||||
|
Dim shl As Shell
|
||||||
|
shl.Initialize("shl","cmd", Array("/c", File.DirApp & "\" & batFile & " " & Main.srvr.Port))
|
||||||
shl.WorkingDirectory = File.DirApp
|
shl.WorkingDirectory = File.DirApp
|
||||||
shl.Run(-1)
|
shl.Run(-1)
|
||||||
Else If Command = "rpm2" Then
|
resp.Write($"Comando '${Command}' ejecutado. Script invocado: ${batFile}"$)
|
||||||
|
Catch
|
||||||
|
resp.Write($"Error al ejecutar el script para '${Command}': ${LastException.Message}"$)
|
||||||
|
End Try
|
||||||
|
Return
|
||||||
|
|
||||||
|
|
||||||
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
||||||
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
||||||
Public shl As Shell
|
Public shl As Shell
|
||||||
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoPM2.bat " & Main.srvr.Port))
|
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoPM2.bat " & Main.srvr.Port))
|
||||||
shl.WorkingDirectory = File.DirApp
|
shl.WorkingDirectory = File.DirApp
|
||||||
shl.Run(-1)
|
shl.Run(-1)
|
||||||
Else If Command = "reviveBow" Then
|
|
||||||
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat"$)
|
Case "paused", "continue"
|
||||||
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat<br><br>"$)
|
resp.ContentType = "text/plain; charset=utf-8"
|
||||||
sb.Append($"!!!BOW REINICIANDO!!!"$)
|
If Command = "paused" Then
|
||||||
Public shl As Shell
|
|
||||||
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoBow.bat " & Main.srvr.Port))
|
|
||||||
shl.WorkingDirectory = File.DirApp
|
|
||||||
shl.Run(-1)
|
|
||||||
Else If Command = "paused" Then
|
|
||||||
GlobalParameters.IsPaused = 1
|
GlobalParameters.IsPaused = 1
|
||||||
sb.Append("Servidor pausado.")
|
resp.Write("Servidor pausado.")
|
||||||
Else If Command = "continue" Then
|
|
||||||
GlobalParameters.IsPaused = 0
|
|
||||||
sb.Append("Servidor reanudado.")
|
|
||||||
Else If Command = "logs" Then
|
|
||||||
If GlobalParameters.mpLogs.IsInitialized Then
|
|
||||||
j.Initialize(GlobalParameters.mpLogs)
|
|
||||||
sb.Append(j.ToString)
|
|
||||||
End If
|
|
||||||
Else If Command = "block" Then
|
|
||||||
Dim BlockedConIP As String = req.GetParameter("IP")
|
|
||||||
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
|
||||||
GlobalParameters.mpBlockConnection.Put(BlockedConIP, BlockedConIP)
|
|
||||||
sb.Append("IP bloqueada: " & BlockedConIP)
|
|
||||||
End If
|
|
||||||
Else If Command = "unblock" Then
|
|
||||||
Dim UnBlockedConIP As String = req.GetParameter("IP")
|
|
||||||
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
|
||||||
GlobalParameters.mpBlockConnection.Remove(UnBlockedConIP)
|
|
||||||
sb.Append("IP desbloqueada: " & UnBlockedConIP)
|
|
||||||
End If
|
|
||||||
Else If Command = "restartserver" Then
|
|
||||||
Log($"Ejecutamos ${File.DirApp}/restarServer.bat"$)
|
|
||||||
sb.Append("Reiniciando servidor...")
|
|
||||||
Else If Command = "runatstartup" Then
|
|
||||||
File.Copy("C:\jrdcinterface", "startup.bat", "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp", "startup.bat")
|
|
||||||
sb.Append("Script de inicio añadido.")
|
|
||||||
Else If Command = "stoprunatstartup" Then
|
|
||||||
File.Delete("C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp", "startup.bat")
|
|
||||||
sb.Append("Script de inicio eliminado.")
|
|
||||||
Else If Command = "totalrequests" Then
|
|
||||||
If GlobalParameters.mpTotalRequests.IsInitialized Then
|
|
||||||
j.Initialize(GlobalParameters.mpTotalRequests)
|
|
||||||
sb.Append(j.ToString)
|
|
||||||
End If
|
|
||||||
Else If Command = "totalblocked" Then
|
|
||||||
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
|
||||||
' j.Initialize(Global.mpBlockConnection)
|
|
||||||
sb.Append(j.ToString)
|
|
||||||
End If
|
|
||||||
Else If Command = "ping" Then
|
|
||||||
sb.Append($"Pong ($DateTime{DateTime.Now})"$)
|
|
||||||
Else If Command = "totalcon" Then ' <<< Modificado: Ahora usa GetPoolStats para cada pool
|
|
||||||
' Verificamos que el mapa global de conexiones esté inicializado.
|
|
||||||
' Aunque no lo poblamos directamente, es un buen chequeo de estado.
|
|
||||||
If GlobalParameters.mpTotalConnections.IsInitialized Then
|
|
||||||
sb.Append("<h2>Estadísticas del Pool de Conexiones por DB:</h2>")
|
|
||||||
|
|
||||||
' Creamos un mapa LOCAL para almacenar las estadísticas de TODOS los pools de conexiones.
|
|
||||||
Dim allPoolStats As Map
|
|
||||||
allPoolStats.Initialize
|
|
||||||
|
|
||||||
' Iteramos sobre cada clave de base de datos que tenemos configurada (DB1, DB2, etc.).
|
|
||||||
For Each dbKey As String In Main.listaDeCP
|
|
||||||
' Obtenemos el conector RDC para la base de datos actual.
|
|
||||||
Dim connector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
|
||||||
|
|
||||||
' Si el conector no está inicializado (lo cual no debería ocurrir si Main.AppStart funcionó),
|
|
||||||
' registramos un error y pasamos al siguiente.
|
|
||||||
If connector.IsInitialized = False Then
|
|
||||||
Log($"Manager: ADVERTENCIA: El conector para ${dbKey} no está inicializado."$)
|
|
||||||
Dim errorMap As Map = CreateMap("Error": "Conector no inicializado o no cargado correctamente")
|
|
||||||
allPoolStats.Put(dbKey, errorMap)
|
|
||||||
Continue ' Salta a la siguiente iteración del bucle.
|
|
||||||
End If
|
|
||||||
|
|
||||||
' Llamamos al método GetPoolStats del conector para obtener las métricas de su pool.
|
|
||||||
Dim poolStats As Map = connector.GetPoolStats
|
|
||||||
|
|
||||||
' Añadimos las estadísticas de este pool (poolStats) al mapa general (allPoolStats),
|
|
||||||
' usando la clave de la base de datos (dbKey) como su identificador.
|
|
||||||
allPoolStats.Put(dbKey, poolStats)
|
|
||||||
Next
|
|
||||||
|
|
||||||
' Inicializamos el generador JSON con el mapa 'allPoolStats' (que ahora sí debería contener datos).
|
|
||||||
' (La variable 'j' ya está declarada en Class_Globals de Manager.bas, no la declares de nuevo aquí).
|
|
||||||
j.Initialize(allPoolStats)
|
|
||||||
|
|
||||||
' Añadimos la representación JSON de las estadísticas al StringBuilder para la respuesta HTML.
|
|
||||||
sb.Append(j.ToString)
|
|
||||||
Else
|
Else
|
||||||
sb.Append("El mapa de conexiones GlobalParameters.mpTotalConnections no está inicializado.")
|
GlobalParameters.IsPaused = 0
|
||||||
|
resp.Write("Servidor reanudado.")
|
||||||
End If
|
End If
|
||||||
|
Return
|
||||||
|
|
||||||
|
Case "block", "unblock"
|
||||||
|
resp.ContentType = "text/plain; charset=utf-8"
|
||||||
|
Dim ip As String = req.GetParameter("IP")
|
||||||
|
If ip = "" Then
|
||||||
|
resp.Write("Error: El parámetro IP es requerido.")
|
||||||
|
Return
|
||||||
End If
|
End If
|
||||||
' =========================================================================
|
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
||||||
' ### FIN DE TU LÓGICA DE COMANDOS ###
|
If Command = "block" Then
|
||||||
' =========================================================================
|
GlobalParameters.mpBlockConnection.Put(ip, ip)
|
||||||
|
resp.Write($"IP bloqueada: ${ip}"$)
|
||||||
|
Else
|
||||||
|
GlobalParameters.mpBlockConnection.Remove(ip)
|
||||||
|
resp.Write($"IP desbloqueada: ${ip}"$)
|
||||||
|
End If
|
||||||
|
Else
|
||||||
|
resp.Write("Error: El mapa de bloqueo no está inicializado.")
|
||||||
|
End If
|
||||||
|
Return
|
||||||
|
|
||||||
' --- Cerramos la página y la enviamos ---
|
Case Else
|
||||||
sb.Append("</div><p class='logout'><a href='/logout'>Cerrar Sesión</a> | <a href=# onclick='toggleForm()'>Cambiar Contraseña</a></p></body></html>")
|
resp.ContentType = "text/plain; charset=utf-8"
|
||||||
resp.Write(sb.ToString)
|
resp.SendError(404, $"Comando desconocido: '{Command}'"$)
|
||||||
|
Return
|
||||||
|
|
||||||
If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now))
|
End Select
|
||||||
End Sub
|
End Sub
|
||||||
@@ -50,6 +50,7 @@ Public Sub ValidateAndAdjustParameters (CommandName As String, DBKey As String,
|
|||||||
res.Success = True
|
res.Success = True
|
||||||
Dim WarningMsg As String = $"ADVERTENCIA: Se recibieron más parámetros de los esperados para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}. Se ajustó la lista de parámetros a ${expectedParams} elementos."$
|
Dim WarningMsg As String = $"ADVERTENCIA: Se recibieron más parámetros de los esperados para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}. Se ajustó la lista de parámetros a ${expectedParams} elementos."$
|
||||||
Log(WarningMsg)
|
Log(WarningMsg)
|
||||||
|
Log("Cache: " & Main.LOG_CACHE_THRESHOLD & "|" & Main.ErrorLogCache.Size)
|
||||||
Main.LogServerError("ADVERTENCIA", "ParameterValidationUtils.ValidateAndAdjustParameters", WarningMsg, DBKey, CommandName, Null) ' <-- Nuevo Log [6]
|
Main.LogServerError("ADVERTENCIA", "ParameterValidationUtils.ValidateAndAdjustParameters", WarningMsg, DBKey, CommandName, Null) ' <-- Nuevo Log [6]
|
||||||
Else
|
Else
|
||||||
' Si la tolerancia NO está habilitada, esto es un error crítico.
|
' Si la tolerancia NO está habilitada, esto es un error crítico.
|
||||||
|
|||||||
281
jRDC_Multi.b4j
281
jRDC_Multi.b4j
@@ -32,10 +32,11 @@ Library8=jsql
|
|||||||
Library9=bcrypt
|
Library9=bcrypt
|
||||||
Module1=Cambios
|
Module1=Cambios
|
||||||
Module10=Manager
|
Module10=Manager
|
||||||
Module11=ParameterValidationUtils
|
Module11=Manager0
|
||||||
Module12=ping
|
Module12=ParameterValidationUtils
|
||||||
Module13=RDCConnector
|
Module13=ping
|
||||||
Module14=TestHandler
|
Module14=RDCConnector
|
||||||
|
Module15=TestHandler
|
||||||
Module2=ChangePassHandler
|
Module2=ChangePassHandler
|
||||||
Module3=DBHandlerB4X
|
Module3=DBHandlerB4X
|
||||||
Module4=DBHandlerJSON
|
Module4=DBHandlerJSON
|
||||||
@@ -46,7 +47,7 @@ Module8=LoginHandler
|
|||||||
Module9=LogoutHandler
|
Module9=LogoutHandler
|
||||||
NumberOfFiles=10
|
NumberOfFiles=10
|
||||||
NumberOfLibraries=9
|
NumberOfLibraries=9
|
||||||
NumberOfModules=14
|
NumberOfModules=15
|
||||||
Version=10.3
|
Version=10.3
|
||||||
@EndOfDesignText@
|
@EndOfDesignText@
|
||||||
'Non-UI application (console / server application)
|
'Non-UI application (console / server application)
|
||||||
@@ -55,7 +56,7 @@ Version=10.3
|
|||||||
|
|
||||||
#CommandLineArgs:
|
#CommandLineArgs:
|
||||||
#MergeLibraries: True
|
#MergeLibraries: True
|
||||||
' VERSION 5.09.16
|
' VERSION 5.09.17
|
||||||
'###########################################################################################################
|
'###########################################################################################################
|
||||||
'###################### PULL #############################################################
|
'###################### PULL #############################################################
|
||||||
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
||||||
@@ -112,6 +113,7 @@ Sub Process_Globals
|
|||||||
' NUEVAS VARIABLES para control granular de logs
|
' NUEVAS VARIABLES para control granular de logs
|
||||||
' Mapa para almacenar el estado de logging (True/False) por cada DBKey (DB1, DB2, etc.).
|
' Mapa para almacenar el estado de logging (True/False) por cada DBKey (DB1, DB2, etc.).
|
||||||
Public SQLiteLoggingStatusByDB As Map
|
Public SQLiteLoggingStatusByDB As Map
|
||||||
|
|
||||||
' Bandera global que indica si AL MENOS una base de datos tiene los logs habilitados.
|
' Bandera global que indica si AL MENOS una base de datos tiene los logs habilitados.
|
||||||
Public IsAnySQLiteLoggingEnabled As Boolean
|
Public IsAnySQLiteLoggingEnabled As Boolean
|
||||||
|
|
||||||
@@ -121,12 +123,26 @@ Sub Process_Globals
|
|||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Public QueryLogCache As List ' Cache para los logs de rendimiento (query_logs)
|
||||||
|
Public ErrorLogCache As List ' Cache para los logs de errores y advertencias
|
||||||
|
Public LOG_CACHE_THRESHOLD As Int = 350 ' Umbral de registros para forzar la escritura
|
||||||
|
|
||||||
|
Dim logger As Boolean
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Sub AppStart (Args() As String)
|
Sub AppStart (Args() As String)
|
||||||
|
#if DEBUG
|
||||||
|
logger = True
|
||||||
|
LOG_CACHE_THRESHOLD = 10
|
||||||
|
#else
|
||||||
|
logger = False
|
||||||
|
#End If
|
||||||
' --- 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")
|
||||||
|
QueryLogCache.Initialize
|
||||||
|
ErrorLogCache.Initialize
|
||||||
|
|
||||||
' 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.
|
||||||
InitializeSQLiteDatabase
|
InitializeSQLiteDatabase
|
||||||
@@ -164,7 +180,7 @@ Sub AppStart (Args() As String)
|
|||||||
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)
|
' Lógica de Logs para DB1 (Fuente principal de configuración)
|
||||||
Dim enableLogsSetting As Int = con1.config.GetDefault("enableSQLiteLogs", 1).As(Int)
|
Dim enableLogsSetting As Int = con1.config.GetDefault("enableSQLiteLogs", 0).As(Int)
|
||||||
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||||
SQLiteLoggingStatusByDB.Put("DB1", isEnabled) ' Guardar el estado
|
SQLiteLoggingStatusByDB.Put("DB1", isEnabled) ' Guardar el estado
|
||||||
|
|
||||||
@@ -276,10 +292,10 @@ Sub AppStart (Args() As String)
|
|||||||
|
|
||||||
If IsAnySQLiteLoggingEnabled Then
|
If IsAnySQLiteLoggingEnabled Then
|
||||||
timerLogs.Enabled = True
|
timerLogs.Enabled = True
|
||||||
Log("Main.AppStart: Timer de limpieza de logs ACTIVADO (al menos una DB requiere logs).")
|
If logger Then Log("Main.AppStart: Timer de limpieza de logs ACTIVADO (al menos una DB requiere logs).")
|
||||||
Else
|
Else
|
||||||
timerLogs.Enabled = False
|
timerLogs.Enabled = False
|
||||||
Log("Main.AppStart: Timer de limpieza de logs DESHABILITADO (ninguna DB requiere logs).")
|
If logger Then Log("Main.AppStart: Timer de limpieza de logs DESHABILITADO (ninguna DB requiere logs).")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' <<<< Fin del bloque del Timer >>>>
|
' <<<< Fin del bloque del Timer >>>>
|
||||||
@@ -321,7 +337,7 @@ Sub InitializeSQLiteDatabase
|
|||||||
SQL1.ExecNonQuery(createUserTable)
|
SQL1.ExecNonQuery(createUserTable)
|
||||||
|
|
||||||
' Crear tabla 'query_logs'
|
' Crear tabla 'query_logs'
|
||||||
Log("Creando tabla 'query_logs' con columnas de rendimiento.")
|
If logger Then Log("Creando tabla 'query_logs' 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)
|
||||||
|
|
||||||
@@ -342,11 +358,11 @@ Sub InitializeSQLiteDatabase
|
|||||||
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.")
|
If logger Then Log("Verificando y migrando tabla 'query_logs' si es necesario.")
|
||||||
|
|
||||||
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.")
|
If logger Then 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)"
|
||||||
|
|
||||||
@@ -371,7 +387,7 @@ Sub InitializeSQLiteDatabase
|
|||||||
rs.Close
|
rs.Close
|
||||||
|
|
||||||
If columnExists = False Then
|
If columnExists = False Then
|
||||||
Log("Añadiendo columna 'busy_connections' a query_logs.")
|
If logger Then 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")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
@@ -388,20 +404,34 @@ Sub InitializeSQLiteDatabase
|
|||||||
rs.Close
|
rs.Close
|
||||||
|
|
||||||
If columnExists = False Then
|
If columnExists = False Then
|
||||||
Log("Añadiendo columna 'handler_active_requests' a query_logs.")
|
If logger Then 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
|
||||||
|
columnExists = False
|
||||||
|
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)")
|
||||||
|
Do While rs.NextRow
|
||||||
|
If rs.GetString("name").EqualsIgnoreCase("timestamp_text_local") Then
|
||||||
|
columnExists = True
|
||||||
|
Exit ' La columna ya existe, salimos del bucle.
|
||||||
|
End If
|
||||||
|
Loop
|
||||||
|
rs.Close
|
||||||
|
|
||||||
|
If columnExists = False Then
|
||||||
|
If logger Then Log("Añadiendo columna 'timestamp_text_local' a query_logs.")
|
||||||
|
' Usamos 'TEXT' para almacenar la cadena de fecha/hora formateada.
|
||||||
|
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN timestamp_text_local TEXT")
|
||||||
|
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.")
|
If logger Then 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.")
|
If logger Then 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
|
||||||
Log("Tabla 'errores' ya existe.")
|
If logger Then 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' <<<
|
||||||
|
|
||||||
@@ -411,45 +441,155 @@ Sub InitializeSQLiteDatabase
|
|||||||
End If
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' --- 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)
|
||||||
|
|
||||||
' Obtener el estado de logging para esta DBKey. Usar False si la DBKey no existe en el mapa.
|
|
||||||
Dim isEnabled As Boolean = SQLiteLoggingStatusByDB.GetDefault(DbKey, False)
|
Dim isEnabled As Boolean = SQLiteLoggingStatusByDB.GetDefault(DbKey, False)
|
||||||
|
|
||||||
If isEnabled Then
|
If isEnabled Then
|
||||||
Try
|
|
||||||
SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip, busy_connections, handler_active_requests) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
' Formato de tiempo necesario para la columna timestamp_text_local
|
||||||
Array As Object(QueryName, DurationMs, DateTime.Now, DbKey, ClientIp, PoolBusyConnections, HandlerActiveRequests))
|
DateTime.DateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||||
Catch
|
Dim formattedTimestamp As String = DateTime.Date(DateTime.Now)
|
||||||
Log("Error al guardar log de query en SQLite (Main.LogQueryPerformance): " & LastException.Message)
|
|
||||||
End Try
|
' 1. Crear el mapa de datos (log entry)
|
||||||
|
Dim logEntry As Map = CreateMap("query_name": QueryName, "duration_ms": DurationMs, "timestamp": DateTime.Now, _
|
||||||
|
"db_key": DbKey, "client_ip": ClientIp, "busy_connections": PoolBusyConnections, _
|
||||||
|
"handler_active_requests": HandlerActiveRequests, "timestamp_text_local": formattedTimestamp)
|
||||||
|
|
||||||
|
' 2. Zona Crítica: Añadir a la caché y verificar el umbral
|
||||||
|
Dim shouldWriteBatch As Boolean = False
|
||||||
|
|
||||||
|
' Usamos el lock global para garantizar que la adición y la verificación del tamaño sean atómicas.
|
||||||
|
MainConnectorsLock.RunMethod("lock", Null)
|
||||||
|
|
||||||
|
QueryLogCache.Add(logEntry)
|
||||||
|
|
||||||
|
If QueryLogCache.Size >= LOG_CACHE_THRESHOLD Then
|
||||||
|
shouldWriteBatch = True
|
||||||
|
End If
|
||||||
|
|
||||||
|
MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
|
|
||||||
|
' 3. Si se alcanzó el umbral, disparamos la escritura.
|
||||||
|
' NO DEBE HACERSE CON EL LOCK PUESTO.
|
||||||
|
If shouldWriteBatch Then
|
||||||
|
CallSub(Me, "WriteQueryLogsBatch")
|
||||||
|
End If
|
||||||
|
|
||||||
End If
|
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'. ---
|
||||||
' ¡MODIFICADA PARA USAR FILTRADO GRANULAR POR DBKEY!
|
|
||||||
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)
|
||||||
|
|
||||||
' Obtener el estado de logging para esta DBKey. Usar False si la DBKey es Null o no está en el mapa.
|
|
||||||
Dim isEnabled As Boolean = SQLiteLoggingStatusByDB.GetDefault(DBKey, False)
|
Dim isEnabled As Boolean = SQLiteLoggingStatusByDB.GetDefault(DBKey, False)
|
||||||
|
|
||||||
If isEnabled Then
|
If isEnabled Then
|
||||||
Try
|
|
||||||
SQL1.ExecNonQuery2("INSERT INTO errores (timestamp, type, source, message, db_key, command_name, client_ip) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
' Log($"[DEBUG CACHE] Se recibió log de error/advertencia para: ${CommandName}"$) '<--- Nuevo Log 1
|
||||||
Array As Object(DateTime.Now, Type0, Source, Message, DBKey, CommandName, ClientIp))
|
|
||||||
Catch
|
Dim logEntry As Map = CreateMap("timestamp": DateTime.Now, "type": Type0, "source": Source, "message": Message, _
|
||||||
Log("ERROR CRÍTICO: Fallo al guardar el log de error/advertencia en SQLite (Main.LogServerError): " & LastException.Message)
|
"db_key": DBKey, "command_name": CommandName, "client_ip": ClientIp)
|
||||||
End Try
|
|
||||||
|
Dim shouldWriteBatch As Boolean = False
|
||||||
|
|
||||||
|
' 1. Zona Crítica: Añadir a la caché y verificar el umbral
|
||||||
|
|
||||||
|
' Usamos el lock para Thread Safety
|
||||||
|
MainConnectorsLock.RunMethod("lock", Null)
|
||||||
|
' Log($"[DEBUG CACHE] Lock adquirido. Tamaño actual de ErrorLogCache: ${ErrorLogCache.Size}"$) '<--- Nuevo Log 2
|
||||||
|
|
||||||
|
ErrorLogCache.Add(logEntry)
|
||||||
|
|
||||||
|
' Log($"[DEBUG CACHE] Log añadido. Nuevo tamaño: ${ErrorLogCache.Size}. Umbral: ${LOG_CACHE_THRESHOLD}"$) '<--- Nuevo Log 3
|
||||||
|
|
||||||
|
If ErrorLogCache.Size >= LOG_CACHE_THRESHOLD Then
|
||||||
|
shouldWriteBatch = True
|
||||||
|
' Log(">>> [DEBUG CACHE] UMBRAL ALCANZADO. DISPARANDO ESCRITURA BATCH. <<<") '<--- Nuevo Log 4
|
||||||
End If
|
End If
|
||||||
|
|
||||||
|
MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
|
' Log($"[DEBUG CACHE] Lock liberado."$) '<--- Nuevo Log 5
|
||||||
|
|
||||||
|
' 2. Si se alcanzó el umbral (o si el timer lo llama), disparamos la escritura.
|
||||||
|
If shouldWriteBatch Then
|
||||||
|
CallSub(Me, "WriteErrorLogsBatch")
|
||||||
|
End If
|
||||||
|
|
||||||
|
Else
|
||||||
|
' Log($"[DEBUG CACHE] Logging deshabilitado para DBKey: ${DBKey}. Log de error omitido."$)
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Public Sub WriteQueryLogsBatch
|
||||||
|
Dim logsToWrite As List
|
||||||
|
logsToWrite.Initialize ' 1. Inicializar la lista local (CRÍTICO)
|
||||||
|
|
||||||
|
' === PASO 1: Intercambio Atómico de Caché (Protegido por ReentrantLock) ===
|
||||||
|
|
||||||
|
MainConnectorsLock.RunMethod("lock", Null)
|
||||||
|
|
||||||
|
If QueryLogCache.Size = 0 Then
|
||||||
|
MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
|
' Log("[DEBUG BATCH-Q] Saliendo: Caché de rendimiento vacía.")
|
||||||
|
Return
|
||||||
|
End If
|
||||||
|
|
||||||
|
' *** CORRECCIÓN CRÍTICA: Copia de contenido (AddAll) en lugar de referencia. ***
|
||||||
|
logsToWrite.AddAll(QueryLogCache)
|
||||||
|
|
||||||
|
Dim batchSize As Int = logsToWrite.Size
|
||||||
|
|
||||||
|
' Vaciamos la caché global. logsToWrite ahora contiene la copia de los elementos.
|
||||||
|
QueryLogCache.Initialize
|
||||||
|
|
||||||
|
MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
|
|
||||||
|
If logger Then Log($"[LOG BATCH] Iniciando escritura transaccional de ${batchSize} logs de rendimiento. Logs copiados: ${logsToWrite.Size}"$)
|
||||||
|
|
||||||
|
' === PASO 2: Escritura Transaccional a SQLite ===
|
||||||
|
|
||||||
|
Try
|
||||||
|
' 1. Iniciar la transacción: Todo lo que siga es una única operación de disco.
|
||||||
|
SQL1.BeginTransaction
|
||||||
|
|
||||||
|
For Each logEntry As Map In logsToWrite
|
||||||
|
SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip, busy_connections, handler_active_requests, timestamp_text_local) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", _
|
||||||
|
Array As Object(logEntry.Get("query_name"), logEntry.Get("duration_ms"), logEntry.Get("timestamp"), logEntry.Get("db_key"), _
|
||||||
|
logEntry.Get("client_ip"), logEntry.Get("busy_connections"), logEntry.Get("handler_active_requests"), _
|
||||||
|
logEntry.Get("timestamp_text_local")))
|
||||||
|
Next
|
||||||
|
|
||||||
|
' 2. Finalizar la transacción: Escritura eficiente a disco.
|
||||||
|
SQL1.TransactionSuccessful
|
||||||
|
|
||||||
|
if logger then Log($"[LOG BATCH] Lote de ${batchSize} logs de rendimiento escrito exitosamente."$)
|
||||||
|
|
||||||
|
Catch
|
||||||
|
' Si falla, deshacemos todos los logs del lote y registramos el fallo.
|
||||||
|
SQL1.Rollback
|
||||||
|
Dim ErrorMsg As String = "ERROR CRÍTICO: Fallo al escribir lote de logs de rendimiento en SQLite: " & LastException.Message
|
||||||
|
Log(ErrorMsg)
|
||||||
|
|
||||||
|
' Usamos LogServerError para que el fallo quede registrado en la tabla 'errores' si el logging está habilitado.
|
||||||
|
LogServerError("ERROR", "Main.WriteQueryLogsBatch", ErrorMsg, Null, "log_batch_write_performance", Null)
|
||||||
|
End Try
|
||||||
|
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
' --- Subrutina de evento para el Timer 'timerLogs'. ---
|
' --- Subrutina de evento para el Timer 'timerLogs'. ---
|
||||||
' El estado 'Enabled' del Timer ya está controlado por IsAnySQLiteLoggingEnabled en AppStart y Manager.
|
' El estado 'Enabled' del Timer ya está controlado por IsAnySQLiteLoggingEnabled en AppStart y Manager.
|
||||||
Sub TimerLogs_Tick
|
Sub TimerLogs_Tick
|
||||||
Try
|
Try
|
||||||
|
' 1. Vaciado de logs de rendimiento (asumiendo que WriteQueryLogsBatch también fue implementado)
|
||||||
|
WriteQueryLogsBatch
|
||||||
|
|
||||||
|
' 2. Vaciado de logs de errores
|
||||||
|
WriteErrorLogsBatch
|
||||||
|
|
||||||
|
' 3. Limpieza y VACUUM (esto ya verifica IsAnySQLiteLoggingEnabled [8])
|
||||||
borraArribaDe15000Logs
|
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)
|
||||||
@@ -457,16 +597,85 @@ Sub TimerLogs_Tick
|
|||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
Public Sub WriteErrorLogsBatch
|
||||||
|
Dim logsToWrite As List
|
||||||
|
logsToWrite.Initialize ' *** Aseguramos que logsToWrite sea una LISTA NUEVA y no dependa de la referencia.
|
||||||
|
|
||||||
|
' === PASO 1: Intercambio Atómico de Caché (Protegido por ReentrantLock) ===
|
||||||
|
|
||||||
|
MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
||||||
|
|
||||||
|
' Log($"[DEBUG BATCH] Lock adquirido en WriteErrorLogsBatch. Caché Size: ${ErrorLogCache.Size}"$)
|
||||||
|
|
||||||
|
If ErrorLogCache.Size = 0 Then
|
||||||
|
MainConnectorsLock.RunMethod("unlock", Null)
|
||||||
|
' Log("[DEBUG BATCH] Saliendo: Caché vacía.")
|
||||||
|
Return
|
||||||
|
End If
|
||||||
|
|
||||||
|
' *** CORRECCIÓN CRÍTICA: Copiamos el CONTENIDO de forma atómica. ***
|
||||||
|
logsToWrite.AddAll(ErrorLogCache) ' <--- ESTO PASA LOS 10 REGISTROS A LA NUEVA LISTA
|
||||||
|
|
||||||
|
' Vaciamos la caché global. logsToWrite AHORA ES INDEPENDIENTE.
|
||||||
|
ErrorLogCache.Initialize
|
||||||
|
|
||||||
|
MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo.
|
||||||
|
|
||||||
|
' Usamos el tamaño de la lista *copiada*.
|
||||||
|
Dim batchSize As Int = logsToWrite.Size
|
||||||
|
|
||||||
|
If logger Then Log($"[LOG BATCH] Iniciando escritura transaccional de ${batchSize} logs de ERRORES a SQLite. Logs copiados: ${logsToWrite.Size}"$)
|
||||||
|
|
||||||
|
' === PASO 2: Escritura Transaccional a SQLite (Usa logsToWrite) ===
|
||||||
|
|
||||||
|
If batchSize = 0 Then
|
||||||
|
Log("ADVERTENCIA: Fallo en la copia de la lista. logsToWrite está vacía. Abortando escritura.")
|
||||||
|
Return
|
||||||
|
End If
|
||||||
|
|
||||||
|
Try
|
||||||
|
' 1. Iniciar la transacción.
|
||||||
|
SQL1.BeginTransaction
|
||||||
|
|
||||||
|
For Each logEntry As Map In logsToWrite
|
||||||
|
' ... (Tu lógica de SQL1.ExecNonQuery2 aquí) ...
|
||||||
|
SQL1.ExecNonQuery2("INSERT INTO errores (timestamp, type, source, message, db_key, command_name, client_ip) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
||||||
|
Array As Object(logEntry.Get("timestamp"), logEntry.Get("type"), logEntry.Get("source"), logEntry.Get("message"), _
|
||||||
|
logEntry.Get("db_key"), logEntry.Get("command_name"), logEntry.Get("client_ip")))
|
||||||
|
Next
|
||||||
|
|
||||||
|
' 2. Confirmar la transacción.
|
||||||
|
SQL1.TransactionSuccessful
|
||||||
|
|
||||||
|
If logger Then Log($"[LOG BATCH] Lote de ${logsToWrite.Size} logs de ERRORES escrito exitosamente."$)
|
||||||
|
|
||||||
|
Catch
|
||||||
|
' 3. Rollback si falla.
|
||||||
|
SQL1.Rollback
|
||||||
|
Dim ErrorMsg As String = "ERROR CRÍTICO: Fallo al escribir lote de logs de ERRORES en SQLite: " & LastException.Message
|
||||||
|
Log(ErrorMsg)
|
||||||
|
End Try
|
||||||
|
|
||||||
|
End Sub
|
||||||
|
|
||||||
' --- Borra los registros más antiguos de la tabla 'query_logs' y hace VACUUM. ---
|
' --- Borra los registros más antiguos de la tabla 'query_logs' y hace VACUUM. ---
|
||||||
' ¡MODIFICADA PARA USAR FILTRADO GLOBAL!
|
' ¡MODIFICADA PARA USAR FILTRADO GLOBAL!
|
||||||
Sub borraArribaDe15000Logs 'ignore
|
Sub borraArribaDe15000Logs 'ignore
|
||||||
|
|
||||||
If IsAnySQLiteLoggingEnabled Then ' Solo ejecutar si al menos una DB requiere logs.
|
If IsAnySQLiteLoggingEnabled Then ' Solo ejecutar si al menos una DB requiere logs.
|
||||||
Log("Recortando la tabla de 'query_logs', límite de 15,000 registros.")
|
If logger Then Log("Recortando la tabla de 'query_logs', límite de 15,000 registros.")
|
||||||
|
' 1. Limpieza de Logs de Rendimiento (query_logs)
|
||||||
|
If logger Then 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 )")
|
SQL1.ExecNonQuery("DELETE FROM query_logs WHERE timestamp NOT in (SELECT timestamp FROM query_logs ORDER BY timestamp desc LIMIT 15000 )")
|
||||||
|
|
||||||
|
' 2. Limpieza de Logs de Errores (errores)
|
||||||
|
If logger Then Log("Recortando la tabla de 'errores', límite de 15,000 registros.")
|
||||||
|
SQL1.ExecNonQuery("DELETE FROM errores WHERE timestamp NOT in (SELECT timestamp FROM errores ORDER BY timestamp desc LIMIT 15000 )")
|
||||||
|
|
||||||
|
' 3. Optimización de disco
|
||||||
SQL1.ExecNonQuery("vacuum;")
|
SQL1.ExecNonQuery("vacuum;")
|
||||||
Else
|
Else
|
||||||
' Si IsAnySQLiteLoggingEnabled es False, el Timer no debería estar activo.
|
' 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.")
|
If logger Then Log("AVISO: Tarea de limpieza de logs omitida. El logging global de SQLite está deshabilitado.")
|
||||||
End If
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
@@ -5,6 +5,7 @@ ModuleBookmarks11=
|
|||||||
ModuleBookmarks12=
|
ModuleBookmarks12=
|
||||||
ModuleBookmarks13=
|
ModuleBookmarks13=
|
||||||
ModuleBookmarks14=
|
ModuleBookmarks14=
|
||||||
|
ModuleBookmarks15=
|
||||||
ModuleBookmarks2=
|
ModuleBookmarks2=
|
||||||
ModuleBookmarks3=
|
ModuleBookmarks3=
|
||||||
ModuleBookmarks4=
|
ModuleBookmarks4=
|
||||||
@@ -20,6 +21,7 @@ ModuleBreakpoints11=
|
|||||||
ModuleBreakpoints12=
|
ModuleBreakpoints12=
|
||||||
ModuleBreakpoints13=
|
ModuleBreakpoints13=
|
||||||
ModuleBreakpoints14=
|
ModuleBreakpoints14=
|
||||||
|
ModuleBreakpoints15=
|
||||||
ModuleBreakpoints2=
|
ModuleBreakpoints2=
|
||||||
ModuleBreakpoints3=
|
ModuleBreakpoints3=
|
||||||
ModuleBreakpoints4=
|
ModuleBreakpoints4=
|
||||||
@@ -35,14 +37,15 @@ ModuleClosedNodes11=
|
|||||||
ModuleClosedNodes12=
|
ModuleClosedNodes12=
|
||||||
ModuleClosedNodes13=
|
ModuleClosedNodes13=
|
||||||
ModuleClosedNodes14=
|
ModuleClosedNodes14=
|
||||||
|
ModuleClosedNodes15=
|
||||||
ModuleClosedNodes2=
|
ModuleClosedNodes2=
|
||||||
ModuleClosedNodes3=9,10,11,12,13,14,15,16
|
ModuleClosedNodes3=
|
||||||
ModuleClosedNodes4=
|
ModuleClosedNodes4=
|
||||||
ModuleClosedNodes5=
|
ModuleClosedNodes5=
|
||||||
ModuleClosedNodes6=
|
ModuleClosedNodes6=
|
||||||
ModuleClosedNodes7=
|
ModuleClosedNodes7=
|
||||||
ModuleClosedNodes8=
|
ModuleClosedNodes8=
|
||||||
ModuleClosedNodes9=
|
ModuleClosedNodes9=
|
||||||
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
|
NavigationStack=DBHandlerJSON,SendSuccessResponse,253,0,DBHandlerJSON,CleanupAndLog,248,0,RDCConnector,Class_Globals,21,0,RDCConnector,Initialize,35,0,Main,LogServerError,453,0,DBHandlerB4X,ExecuteBatch2,342,0,DBHandlerJSON,Class_Globals,7,0,DBHandlerB4X,ExecuteBatch,445,6,DBHandlerJSON,Handle,201,6,Main,borraArribaDe15000Logs,623,0,Cambios,Process_Globals,22,0
|
||||||
SelectedBuild=0
|
SelectedBuild=0
|
||||||
VisibleModules=3,4,13,1,10,11,14,2
|
VisibleModules=3,4,14,1,10,12
|
||||||
|
|||||||
Reference in New Issue
Block a user