Skip to main content

14. Mantenimiento y operaciones

14.1. Logging y monitoreo

El framework implementa un sistema de logging robusto para facilitar el monitoreo y la solución de problemas en diferentes entornos. Este sistema está diseñado para capturar información relevante y estructurada sobre errores, transacciones y eventos del sistema.

Arquitectura del sistema de logging

La clase principal de logging está implementada como un singleton que gestiona diferentes tipos de logs:

namespace App\Helpers;

class Log
{
private static $instance = null;
private $logDirectory;

private function __construct()
{
$directory = LOG_DIR;
$this->logDirectory = rtrim($directory, '/') . '/';
if (!is_dir($this->logDirectory)) {
if (!mkdir($this->logDirectory, 0755, true)) {
throw new \Exception("No se pudo crear el directorio: $this->logDirectory", 500);
}
}
}

public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}

protected function write($message, $type = 'error')
{
$filename = $this->logDirectory . $type . '_' . date('Y-m-d') . '.log';
$logEntry = '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;
file_put_contents($filename, $logEntry, FILE_APPEND);
}

public static function error($message)
{
try {
$instance = self::getInstance();
$instance->write($message, 'error');
} catch (\Throwable $th) {
throw $th;
}
}

public static function info($message, $type = 'info')
{
$pre = ucfirst($type);
$message = is_array($message) ? "{$pre} " . json_encode($message) : "{$pre} " . $message;
$instance = self::getInstance();
$instance->write($message, $type);
}

public static function transaction($transaction)
{
$instance = self::getInstance();
$instance->writeTransaction($transaction);
}

protected function writeTransaction($message)
{
$filename = $this->logDirectory . 'transaction_' . date('Y-m-d') . '.json';
// Código para escribir transacciones en formato JSON...
}
}

Tipos de logs

El sistema soporta varios tipos de logs especializados:

  1. Logs de errores - Capturan información detallada sobre excepciones y errores:

    Log::error("Error al procesar la petición: " . $exception->getMessage());
  2. Logs informativos - Registran eventos y datos del sistema:

    Log::info("Usuario autenticado: " . $userId);
  3. Logs de transacciones - Almacenan registros detallados de las operaciones CRUD:

    Log::transaction($transactionData);
  4. Logs personalizados - Permiten definir categorías adicionales según necesidad:

    Log::info($emailData, 'email');

Configuración del directorio de logs

El sistema utiliza una configuración centralizada del directorio de logs:

// bootstrap.php
define('LOG_DIR', __DIR__ . '/../../quptmp.outs/logs/api2/');
// comprueba que exista el directorio para logs o crearlo.
checkDirPath(LOG_DIR);

// Función auxiliar para verificar/crear directorios
function checkDirPath($ruta, $create = true)
{
try {
if (!is_dir($ruta) && $create) {
if (mkdir($ruta, 0775, true)) {
}
}
} catch (\Throwable $th) {
echo "Error creando directorio {$ruta}";
throw $th;
}
return;
}

Integración con el ciclo de vida de la aplicación

El sistema de logs está integrado con el manejador de excepciones del framework:

try {
$response = $kernel->handle($request);
QuApp::setSuccessResponse($response);
} catch (\Throwable $th) {
if (ENVIRONMENT === "DEV") {
QuApp::setTraceErrors(true);
}
QuApp::setErrorResponse($th);
}

Y con el parser de errores:

$errorInfo = ErrorParser::parse($error);
Log::error($errorInfo);

14.2. Gestión de configuración

El framework implementa un sistema flexible para gestionar configuraciones en diferentes entornos.

Sistema de configuración

La clase Config actúa como punto central para acceder a configuraciones:

namespace App\Helpers;

class Config
{
public static function __callStatic($name, $arguments)
{
$file = APP_DIR . 'Config/' . $name . ".php";
if (is_file($file)) {
return require $file;
}
// Lanzamos una excepción
throw new \Exception("Config '{$name}' not found.");
}
}

Este método permite acceder a diferentes configuraciones de manera sencilla:

$allowedOrigins = Config::allowedOrigins();
$tokenConfig = Config::token();
$rateLimitConfig = Config::rateLimit();

Archivos de configuración

El framework organiza las configuraciones en archivos PHP independientes:

  1. allowedOrigins.php - Define los orígenes permitidos para solicitudes CORS:

    return "*";
    // o
    return [
    // 'https://dev-helpdesk-tenant-1.josemanuelmunoz.com',
    // 'https://app-btn-google-auth.josemanuelmunoz.com',
    // 'http://localhost:5173',
    ];
  2. token.php - Configuración de tokens de autenticación:

    return [
    "tokenExpires" => '1 day',
    "refreshTokenExpires" => '365 days'
    ];
  3. rateLimit.php - Configuración de límites de tasa:

    return [
    "limit" => 100,
    "duration" => 60
    ];
  4. noAuthRoutes.php - Rutas que no requieren autenticación:

    return [
    "auth/access",
    "auth/access/by-api-key",
    "auth/access/refresh",
    "auth/google/callback",
    "auth/google/with-g-token",
    "auth/google/get-qupgoolou",
    ];
  5. defaultHeaders.php - Cabeceras HTTP predeterminadas:

    return [
    'Content-Type' => 'application/json; charset=utf-8',
    'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With, X-Application, X-Tienda, X-auth, Access-Control-Allow-Origin, Traitrefresh',
    'Access-Control-Allow-Credentials' => "true",
    ];
  6. resolvers.php - Configuración de inyección de dependencias:

    return [
    '/products' => [
    InterfaceResDto::class => \ApiLayer\Domains\Products\DTOs\MaesartiProductosDTO::class,
    InterfaceReqDto::class => \ApiLayer\Domains\Products\DTOs\CreateMaesartiProductosDTO::class,
    ],
    '/permut/products' => [
    InterfaceResDto::class => \ApiLayer\Modules\Permut\Domains\Products\DTOs\MaesartiProductosDTO::class,
    InterfaceReqDto::class => \ApiLayer\Modules\Permut\Domains\Products\DTOs\CreateMaesartiProductosDTO::class,
    ],
    ];

Configuración de entorno

El framework detecta automáticamente el entorno de ejecución:

static public function setEnvironment($env = null)
{
self::$environment = 'PROD';

if (isset(self::$QuupIni['id_domain'])) {
if (self::$QuupIni['id_domain'] == 3) {
self::$environment = 'DEV';
}
}

if ($env) {
if ('PROD' === strtoupper($env)) {
self::$environment = 'PROD';
return;
}
if ('DEV' === strtoupper($env)) {
self::$environment = 'DEV';
return;
}
}
return;
}

Y adapta su comportamiento según el entorno:

if (ENVIRONMENT == 'DEV') {
$converter = new QuErrorsToException(E_ALL); //Todo menos notice
} else {
$converter = new QuErrorsToException(E_ALL & ~(E_NOTICE | E_WARNING)); // todo menos warning y notice
}
$converter->register();

14.3. Ciclo de vida en producción

El framework implementa varios mecanismos para gestionar el ciclo de vida de las peticiones en entornos de producción.

Gestión de errores en producción

En producción, los errores se gestionan de manera diferente para ocultar detalles sensibles:

if (ENVIRONMENT != "DEV") {
unset($arError["file"]);
unset($arError["line"]);
unset($arError["type"]);
if (isset($arError["trace"])) {
unset($arError["trace"]);
}
}

Manejo de finalización de scripts

El framework implementa un manejador de finalización para capturar errores fatales:

function handleShutdown()
{
if (!QuApp::$normalTermination) {
$lastError = error_get_last();
$msg = ob_get_contents();

if ($lastError !== null) {
$error = $lastError;
} else if ($msg) {
ob_end_clean();
$error = ErrorContent::get($msg);
} else {
$error = false;
}

if ($error) {
QuApp::setErrorResponse($error);
QuApp::sendResponseAndEnd();
exit(0);
}
}
}
register_shutdown_function('handleShutdown');

Buffer de salida

Para controlar la salida y prevenir interrupciones en la respuesta:

ob_start();

Control de errores de visualización

En entornos de producción, se deshabilita la visualización directa de errores:

if (ENVIRONMENT != "DEV") {
ini_set("display_errors", 0); // solo producción
}

14.4. Estrategias de despliegue

El framework está diseñado para facilitar el despliegue en diferentes entornos.

Estructura de archivos para despliegue

La estructura de archivos está organizada para facilitar el despliegue:

api2/
├── bootstrap.php # Inicialización del framework
├── index.php # Punto de entrada principal
├── artesano # CLI para tareas de mantenimiento
├── keys/ # Claves para autenticación
│ ├── .htaccess # Protección de acceso directo
│ ├── private_key.pem
│ └── public_key.pem
├── src/ # Código fuente del framework
│ ├── ApiLayer/ # Capa de API
│ ├── App/ # Núcleo del framework
│ ├── Infrastructures/ # Capa de infraestructura
│ └── Services/ # Servicios
├── vendor/ # Dependencias (Composer)

Control de acceso a recursos sensibles

El framework implementa protecciones para archivos sensibles:

// api2/.htaccess
RewriteEngine On

# Captura el header de Autorización y lo pasa como una variable de entorno
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [E=HTTP_AUTHORIZATION:%1]

# Denegar acceso directo a env.php
RewriteCond %{REQUEST_URI} \.json$
RewriteRule ^ - [F,L]

# Denegar acceso a archivos .md
RewriteCond %{REQUEST_URI} \.md$
RewriteRule ^ - [F,L]

# Si no es un archivo real...
RewriteCond %{REQUEST_FILENAME} !-f
# Y no es un directorio...
RewriteCond %{REQUEST_FILENAME} !-d
# Entonces, reescribe la solicitud a index.php
RewriteRule ^ index.php [QSA,L]

Y para proteger directamente las claves de firma:

# keys/.htaccess
Deny from all

Gestión de dependencias

Las dependencias se gestionan mediante Composer:

{
"packages": [
{
"name": "psr/container",
"version": "1.1.1"
},
{
"name": "symfony/console",
"version": "v4.4.49"
},
{
"name": "symfony/validator",
"version": "v4.4.48"
}
]
}

Confianza en proxies

El framework está configurado para funcionar correctamente detrás de proxies:

// Configurar Request para confiar en las cabeceras de proxy:
Request::setTrustedProxies(
['127.0.0.1', 'REMOTE_ADDR'],
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT
);

Detección de entorno Docker

El framework incluye capacidades para detectar si está ejecutándose dentro de un contenedor Docker:

private function isRunningInDocker()
{
// Método 1: Verificar la existencia de /.dockerenv
if (file_exists('/.dockerenv')) {
return true;
}

// Método 2: Buscar 'docker' en cgroup
if (file_exists('/proc/1/cgroup')) {
$contents = file_get_contents('/proc/1/cgroup');
if (strpos($contents, 'docker') !== false) {
return true;
}
}

// Método 3: Verificar si el hostname comienza con un ID de contenedor
$hostname = gethostname();
if (strlen($hostname) === 12 && ctype_xdigit($hostname)) {
return true;
}

// Método 4: Buscar variables de entorno comunes de Docker
$dockerEnvVars = ['DOCKER_CONTAINER', 'DOCKER_IMAGE_NAME', 'DOCKER_IMAGE_VERSION'];
foreach ($dockerEnvVars as $var) {
if (getenv($var) !== false) {
return true;
}
}

return false;
}

Esta detección es útil para ajustar automáticamente configuraciones como la conexión a la base de datos:

$host = $_SESSION['quup']['sqlServer'];
if ($this->isRunningInDocker() && $host === 'localhost') {
$host = "127.0.0.1";
}

Integración de operaciones

El framework está diseñado para facilitar la integración con sistemas de operaciones y monitoreo mediante:

  1. Logs estructurados: Los logs están organizados por tipo y fecha, facilitando su procesamiento por herramientas de monitoreo.

  2. Identificadores únicos de transacción: Cada operación recibe un UUID para rastreo.

  3. Información detallada de contexto: Se captura IP, agente de usuario, origen de la solicitud y otros datos relevantes.

  4. Separación clara de entornos: La detección automática del entorno permite configuraciones específicas.

  5. Gestión de errores y excepciones: Todos los errores son capturados, registrados y manejados apropiadamente.

Con estas características, el framework proporciona una base sólida para operaciones y mantenimiento en entornos de producción, combinando una buena experiencia de desarrollo con robustez en producción.