mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-17 21:06:24 +00:00
- 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.
This commit is contained in:
14
Cambios.bas
14
Cambios.bas
@@ -26,6 +26,20 @@ 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
|
||||
' lista, por ejemplo 15, 30, 45 y 60 minutos antes.
|
||||
|
||||
' - 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
|
||||
' - 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.).
|
||||
|
||||
@@ -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)
|
||||
|
||||
' Devuelve un resumen para el log.
|
||||
Return $"batch (size=${commands.Size})"$
|
||||
' 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})"$
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' --- Subrutinas para manejar la ejecución de queries y batches (Protocolo V1 - Compilación Condicional) ---
|
||||
@@ -422,6 +432,7 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
|
||||
' Lee cuántos comandos vienen en el lote.
|
||||
Dim numberOfStatements As Int = ReadInt(in)
|
||||
Dim res(numberOfStatements) As Int ' Array para resultados (aunque no se usa).
|
||||
Dim singleQueryName As String = ""
|
||||
|
||||
Try
|
||||
con.BeginTransaction
|
||||
@@ -430,6 +441,9 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
|
||||
' Lee el nombre del comando y la lista de parámetros usando el deserializador V1.
|
||||
Dim queryName As String = ReadObject(in)
|
||||
Dim params As List = ReadList(in)
|
||||
If numberOfStatements = 1 Then
|
||||
singleQueryName = queryName 'Capturamos el nombre del query.
|
||||
End If
|
||||
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
|
||||
|
||||
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE (V1) >>>
|
||||
@@ -475,7 +489,12 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
|
||||
SendPlainTextError(resp, 500, LastException.Message)
|
||||
End Try
|
||||
|
||||
Return $"batch (size=${numberOfStatements})"$
|
||||
' Return $"batch (size=${numberOfStatements})"$
|
||||
If numberOfStatements = 1 And singleQueryName <> "" Then
|
||||
Return $"batch (size=1) - query: ${singleQueryName}"$
|
||||
Else
|
||||
Return $"batch (size=${numberOfStatements})"$
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' Ejecuta una consulta única usando el protocolo V1.
|
||||
|
||||
@@ -11,13 +11,18 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
||||
#GOHAN ---> server
|
||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205: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
|
||||
InitialPoolSize=3
|
||||
MinPoolSize=2
|
||||
MaxPoolSize=15
|
||||
AcquireIncrement=2
|
||||
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||
InitialPoolSize=1
|
||||
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||
MinPoolSize=1
|
||||
# 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).
|
||||
@@ -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).
|
||||
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
|
||||
User=SALMA
|
||||
Password=SALMAD2016M
|
||||
|
||||
@@ -11,13 +11,30 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
||||
#GOHAN ---> server
|
||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205: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
|
||||
InitialPoolSize=3
|
||||
MinPoolSize=2
|
||||
MaxPoolSize=15
|
||||
AcquireIncrement=2
|
||||
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||
InitialPoolSize=1
|
||||
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||
MinPoolSize=1
|
||||
# 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
|
||||
User=SALMA
|
||||
|
||||
@@ -11,13 +11,30 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
||||
#GOHAN ---> server
|
||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205: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
|
||||
InitialPoolSize=3
|
||||
MinPoolSize=2
|
||||
MaxPoolSize=15
|
||||
AcquireIncrement=2
|
||||
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||
InitialPoolSize=1
|
||||
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||
MinPoolSize=1
|
||||
# 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
|
||||
User=SALMA
|
||||
|
||||
@@ -11,13 +11,18 @@ DriverClass=oracle.jdbc.driver.OracleDriver
|
||||
#GOHAN ---> server
|
||||
#JdbcUrl=jdbc:oracle:thin:@//10.0.0.205: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
|
||||
InitialPoolSize=3
|
||||
MinPoolSize=2
|
||||
MaxPoolSize=15
|
||||
AcquireIncrement=2
|
||||
# Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||
InitialPoolSize=1
|
||||
# Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||
MinPoolSize=1
|
||||
# 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).
|
||||
@@ -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).
|
||||
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
|
||||
User=GUNA
|
||||
Password=GUNAD2015M
|
||||
|
||||
259
jRDC_Multi.b4j
259
jRDC_Multi.b4j
@@ -55,7 +55,7 @@ Version=10.3
|
||||
|
||||
#CommandLineArgs:
|
||||
#MergeLibraries: True
|
||||
' VERSION 5.09.16
|
||||
' VERSION 5.09.16.2
|
||||
'###########################################################################################################
|
||||
'###################### PULL #############################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
||||
@@ -112,6 +112,7 @@ Sub Process_Globals
|
||||
' 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
|
||||
|
||||
@@ -121,12 +122,25 @@ Sub Process_Globals
|
||||
ErrorMessage As String, _
|
||||
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 Const LOG_CACHE_THRESHOLD As Int = 10 ' Umbral de registros para forzar la escritura
|
||||
|
||||
Dim logger As Boolean
|
||||
End Sub
|
||||
|
||||
Sub AppStart (Args() As String)
|
||||
#if DEBUG
|
||||
logger = True
|
||||
#else
|
||||
logger = False
|
||||
#End If
|
||||
' --- Subrutina principal que se ejecuta al iniciar la aplicación ---
|
||||
|
||||
bc.Initialize("BC")
|
||||
QueryLogCache.Initialize
|
||||
ErrorLogCache.Initialize
|
||||
|
||||
' 1. Inicializa la base de datos local de usuarios (SQLite) y la tabla de logs.
|
||||
InitializeSQLiteDatabase
|
||||
@@ -276,10 +290,10 @@ Sub AppStart (Args() As String)
|
||||
|
||||
If IsAnySQLiteLoggingEnabled Then
|
||||
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
|
||||
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
|
||||
|
||||
' <<<< Fin del bloque del Timer >>>>
|
||||
@@ -321,7 +335,7 @@ Sub InitializeSQLiteDatabase
|
||||
SQL1.ExecNonQuery(createUserTable)
|
||||
|
||||
' 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)"
|
||||
SQL1.ExecNonQuery(createQueryLogsTable)
|
||||
|
||||
@@ -342,11 +356,11 @@ Sub InitializeSQLiteDatabase
|
||||
Log("Base de datos de usuarios cargada.")
|
||||
|
||||
' >>> 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
|
||||
|
||||
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)"
|
||||
|
||||
@@ -371,7 +385,7 @@ Sub InitializeSQLiteDatabase
|
||||
rs.Close
|
||||
|
||||
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")
|
||||
End If
|
||||
|
||||
@@ -388,20 +402,34 @@ Sub InitializeSQLiteDatabase
|
||||
rs.Close
|
||||
|
||||
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")
|
||||
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 <<<
|
||||
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
|
||||
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)"
|
||||
SQL1.ExecNonQuery(createErrorsTable)
|
||||
Else
|
||||
Log("Tabla 'errores' ya existe.")
|
||||
If logger Then Log("Tabla 'errores' ya existe.")
|
||||
End If
|
||||
' >>> FIN: Lógica de migración para 'errores' <<<
|
||||
|
||||
@@ -411,45 +439,155 @@ Sub InitializeSQLiteDatabase
|
||||
End If
|
||||
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)
|
||||
|
||||
' 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)
|
||||
|
||||
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 (?, ?, ?, ?, ?, ?, ?)", _
|
||||
Array As Object(QueryName, DurationMs, DateTime.Now, DbKey, ClientIp, PoolBusyConnections, HandlerActiveRequests))
|
||||
Catch
|
||||
Log("Error al guardar log de query en SQLite (Main.LogQueryPerformance): " & LastException.Message)
|
||||
End Try
|
||||
|
||||
' Formato de tiempo necesario para la columna timestamp_text_local
|
||||
DateTime.DateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||
Dim formattedTimestamp As String = DateTime.Date(DateTime.Now)
|
||||
|
||||
' 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 Sub
|
||||
|
||||
' --- 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)
|
||||
|
||||
' 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)
|
||||
|
||||
If isEnabled Then
|
||||
Try
|
||||
SQL1.ExecNonQuery2("INSERT INTO errores (timestamp, type, source, message, db_key, command_name, client_ip) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
||||
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
|
||||
|
||||
' Log($"[DEBUG CACHE] Se recibió log de error/advertencia para: ${CommandName}"$) '<--- Nuevo Log 1
|
||||
|
||||
Dim logEntry As Map = CreateMap("timestamp": DateTime.Now, "type": Type0, "source": Source, "message": Message, _
|
||||
"db_key": DBKey, "command_name": CommandName, "client_ip": ClientIp)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
' --- Subrutina de evento para el Timer 'timerLogs'. ---
|
||||
' El estado 'Enabled' del Timer ya está controlado por IsAnySQLiteLoggingEnabled en AppStart y Manager.
|
||||
Sub TimerLogs_Tick
|
||||
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
|
||||
|
||||
Catch
|
||||
Dim ErrorMsg As String = "ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message
|
||||
Log(ErrorMsg)
|
||||
@@ -457,16 +595,77 @@ Sub TimerLogs_Tick
|
||||
End Try
|
||||
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. ---
|
||||
' ¡MODIFICADA PARA USAR FILTRADO GLOBAL!
|
||||
Sub borraArribaDe15000Logs 'ignore
|
||||
|
||||
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.")
|
||||
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.")
|
||||
If logger Then 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=
|
||||
ModuleClosedNodes8=
|
||||
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=Main,TimerLogs_Tick,533,0,Main,LogQueryPerformance,414,5,Main,LogServerError,459,0,Main,AppStart,244,0,Main,WriteQueryLogsBatch,512,1,Main,borraArribaDe15000Logs,617,0,Main,WriteErrorLogsBatch,602,1,Main,InitializeSQLiteDatabase,365,0,Main,Process_Globals,76,4,Cambios,Process_Globals,22,6
|
||||
SelectedBuild=0
|
||||
VisibleModules=3,4,13,1,10,11,14,2
|
||||
|
||||
Reference in New Issue
Block a user