DEBUG para la capa de datos

Para realizar un seguimiento de las instrucciones que vamos ejecutando sobre la BD, es necesario implementar un sistema de trazado que nos aporte la mayor cantidad de información posible para solventar problemas que puedan surgir durante la ejecución de nuestra aplicación web.

19-Sep-2016

Para realizar un seguimiento de las instrucciones que vamos ejecutando sobre la BD, es necesario implementar un sistema de trazado que nos aporte la mayor cantidad de información posible para solventar los posibles problemas que puedan surgir durante la ejecución de nuestra aplicación web.

Estos problemas pueden producirse en el momento de realizar la conexión a la base de datos o a la hora de ejecutar una consulta de cualquier tipo, por lo que necesitamos conocer el script donde se ha producido el error, cuando y que método es el que se ha visto involucrado en este error.

El sistema de trazado que vamos a implementar en la clase principal CnnMySQL vista en el módulo anterior, nos permitirá ir mostrando las ordenes y/o errores en pantalla a medida que se vayan ejecutando o enviar el resultado a un archivo log que posteriormente podemos analizar.

Este sistema de trazado o debug que se activará a través del archivo de configuraciones cnn.json por medio de dos nuevas propiedades, implicará la creación de nuevas propiedades, métodos y líneas de código puestas en puntos estratégicos de nuestra clase.

Implementar DEBUG

Archivo de configuraciones

Como hemos comentado el sistema de trazado lo vamos a activar desde el archivo de configuraciones. Añadiremos una nueva sección ‘[Debug]’ con dos propiedades, la primera activará/desactivará el debug y la segunda indicará si la salida se tiene que enviar a pantalla o a el archivo log.

Contenido del archivo cnn.ini:

[BaseDatos]
host = dirección del host
bd = nombre de la base de datos
usuario = usuario de acceso a la bd
clave = contraseña del usuario

[Debug]
Debug = true o false
Log = true o false

Nuevas constantes y propiedades

Al igual que hicimos en la clase principal, definiremos una constante de nombre FILELOG para hacer referencia al nombre del archivo log donde se almacenarán las líneas del trazado.

define("FILECFG","cnn.ini");
define("FILELOG","cnn.log");

class CnnMySQL extends mysqli_sql_exception {
    ..
    ..
    ..
}

Para el control del sistema de trazado o debug también vamos a definir una serie de propiedades estáticas que se utilizarán posteriormente a lo largo de la clase en los puntos estratégicos de trazado y en el método de impresión de la traza.

class CnnMySQL extends mysqli_sql_exception {
#------------------------------------------------------------------------------------------------
#     PROPIEDADES DE LA CLASE
#------------------------------------------------------------------------------------------------

# PRIVADAS
#------------------------------------------------------------------------------------------------
#  Conexión a la BD
private $Servidor;
private $BaseDatos;
private $Usuario;
private $Clave;
# Objeto que representa la conexión actual abierta
private static $Conector;
# Indica el total de solicitudes de conexión actuales
private static $SolicitudConectar=0;
# Utilizadas para la realización del seguimiento (traza) de la comunicación con la BD
private static $Debug=false;     # Indicador para realizar DEBUG
private static $DebugLog=false# Indicador para enviar la salida al archivo log
private static $FichLog=""# Nombre del archivo log
private static $Tinicio=0;   # Tiempo de inicio en milisegundos de la traza
private static $Tfinal=0;    # Tiempo de finalización en milisegundos de la traza

Cargar las configuraciones

Al método privado CargarCfg le añadiremos las líneas encargadas de inicializar las propiedades del debug.

private function CargarCfg() {
    ...
    ...
    ...
    $this->BaseDatos=$cfg_db['BaseDatos']['bd'];
    $this->Usuario=$cfg_db['BaseDatos']['usuario'];
    $this->Clave=$cfg_db['BaseDatos']['clave'];
    # Si se localiza la sección Debug cargamos sus valores
if(isset($cfg_db['Debug'])) {     # Cargamos los indicadores para realizar una traza (DEBUG) de la ejecución self::$Debug=$cfg_db['Debug']['debug']; self::$DebugLog=$cfg_db['Debug']['log']; # Inicializamos los marcadores de tiempo en milisegundos para calcular el tiempo transcurrido entre cada mensaje DEBUG self::$Tinicio=microtime(true); self::$Tfinal=self::$Tinicio; # Inicializamos el nombre del archivo log de la traza # Se creará en la misma carpeta que el archivo de configuraciones self::$FichLog = __DIR__.DIRECTORY_SEPARATOR.FILELOG; } }

Método de trazado

Este método será el encargado de mostrar el mensaje de la instrucción que se ha ejecutado y su estado o de enviarlo al archivo log.

Recibe como argumento el mensaje a mostrar.

# Imprimir la información de seguimiento de ejecución
#----------------------------------------------------------------
private function PrintDebug($Cadena) {
    # Si no se ha activado el indicador de trazado cancelamos
    if(!self::$Debug) return;
    # Si la salida tiene que enviarse al archivo log
    if(self::$DebugLog) {
       # Obtiene el momento de la impresión en milisegundos
       self::$Tfinal=microtime(true);
       # Creamos línea con la siguiente información: 
       # * Script desde el que se ejecuta
       # * Fecha y hora 
       # * Milisegundos transcurridos entre la impresión actual y la anterior 
       # * Mensaje
       $Linea=sprintf("%s :: %s :: %0.4f :: %s", $_SERVER['SCRIPT_NAME'], date('d-m-Y H:i:s'), round((self::$Tfinal - self::$Tinicio), 4), $Cadena);
       # Añadimos la línea al final del archivo log
       file_put_contents(self::$FichLog,$Linea.PHP_EOL,FILE_APPEND);
       # Guardamos el intervalo de tiempo actual para la siguiente impresión
       self::$Tinicio=self::$Tfinal;
    } else {
       # Mostramos mensaje directamente en pantalla
       echo '<pre>'.$Cadena.'</pre>';
    }
}

Puntos de trazado

Ahora que ya tenemos todo lo necesario para realizar el seguimiento de las instrucciones, es el momento de ir colocando en puntos concretos de la clase, llamadas al método PrintDebug para comprobar el estado de lo que se está ejecutando.

En cada punto de control realizaremos una llamada al método PrintDebug con el mensaje de la traza.

Vamos a colocar puntos de control en los siguientes métodos de la clase principal: AbrirConexion, CerrarConexion, TotRegSelect, TotRegAccion, LiberaRecurso y EjecutarConsulta.

AbrirConexion

private function AbrirConexion() {
    self::$SolicitudConectar++;
    # Generamos debug de la situación
    $this->PrintDebug('Solicitud conexión: '. self::$SolicitudConectar);
    if(!empty(self::$Conector)) { 
       # Generamos debug de la situación
       $this->PrintDebug('Utilizando conexión abierta.');       
       return;
    } 
    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
    try {
       self::$Conector= new mysqli($this->Servidor, $this->Usuario, $this->Clave, $this->BaseDatos); 
       self::$Conector->query("SET NAMES 'UTF8'");
       # Generamos debug de la situación
       $this->PrintDebug('Conexión abierta.');
    } catch (mysqli_sql_exception $e) {
       # Generamos debug con el mensaje de error indicando el nombre del método en el que se ha producido
       $this->PrintDebug('ERROR ('.__METHOD__.'): '.$e->getMessage());
       printf("Fallo en la conexión: %s<br/>Servidor: %s<br/>Base Datos: %s", $e->getMessage(),$this->Servidor,$this->BaseDatos);
       exit();
    } 
}

CerrarConexion

private function CerrarConexion() {
  self::$SolicitudConectar--;
  # Generamos debug de la situación mostrando las instancias pendientes de cierre
  $this->PrintDebug('Cerrando conexión. Solicitudes pendientes: '.(self::$SolicitudConectar));
  if(self::$SolicitudConectar==0) {
     @self::$Conector->close();
     @self::$Conector=NULL;
     # Generamos debug de la situación
     $this->PrintDebug('Conexión cerrada.');
  }
}

TotRegSelect

private function TotRegSelect(&$result) {
  if(empty($result)) return 0; 
  $Total=0;
  try {
     $Total=$result->num_rows;
  } catch (mysqli_sql_exception $e) {
     # Generamos debug con el mensaje de error
     $this->PrintDebug('ERROR ('.__METHOD__.'): '.$e->getMessage());    
  }
  # Generamos debug con el total de registros seleccionados
  $this->PrintDebug($Total.' registros seleccionados.');
  return $Total;
}

TotRegAccion

private function TotRegAccion() {
  $Total=0;
  try {
     $Total=self::$Conector->affected_rows;
  }  catch (mysqli_sql_exception $e) {
     # Generamos debug con el mensaje de error
     $this->PrintDebug('ERROR ('.__METHOD__.'): '.$e->getMessage());    
  }
  # Generamos debug con el total de registros afectados
  $this->PrintDebug($Total.' registros afectados.');
  return $Total;
}

LiberaRecurso

private function LiberaRecurso(&$result) {
  if(!empty($result)) {
     try {
        $result->free();
        # Generamos debug de la situación
        $this->PrintDebug('Recurso liberado.');
     } catch (mysqli_sql_exception $e) {
        # Generamos debug con el mensaje de error
        $this->PrintDebug('ERROR ('.__METHOD__.'): '.$e->getMessage());    
     }
  }
}

EjecutarConsulta

private function EjecutarConsulta($SQL) {
    # Generamos debug mostrando la consulta SQL que se va a ejecutar
    $this->PrintDebug($SQL);
    # Inicializamos las propiedades antes de ejecutar la consulta
    $this->TotRegistros=0;
    $this->TotColumnas=0;
    $this->Columnas=[];
    # Inicializamos la variable donde se almacenará el objeto mysqli_result
    $result=NULL;
    try {
    # Ejecutamos la consulta SQL
       $result=self::$Conector->query($SQL);
    # Si es una consulta de selección
       if(substr(strtoupper($SQL),0,6)==='SELECT') {
          $this->TotRegistros=$this->TotRegSelect($result);
          $this->Estado=($this->TotRegistros>0);
          $this->TotColumnas=$this->TotalColumnas($result);
          $this->Columnas=$this->NombreColumnas($result);
       } else {
          $this->Estado=$result;
          $this->PrintDebug(($result)? 'Ejecución Correcta':'Error de ejecución');
          $this->TotRegistros=$this->TotRegAccion();
       }
    } catch (mysqli_sql_exception $e) {
     # Generamos debug con el mensaje de error
     $this->PrintDebug('ERROR ('.__METHOD__.'): '.$e->getMessage());    
  }
  return $result;
}