loading...
Cover image for Como generar logs que registren las consultas a base de datos en Laravel

Como generar logs que registren las consultas a base de datos en Laravel

lito profile image Lito ・2 min read

Con este post crearemos un fichero de log por cada día y URL que nos permitirá descubrir problemas como consultas N+1 y analizar si estamos ejecutando una carga excesiva a la base de datos.

1. Creamos el Logger propio de Base de Datos

Lo podemos dar de alta por ejemplo en app/Services/Database/Logger.php:

<?php declare(strict_types=1);

namespace App\Services\Database;

use DateTime;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Request;

class Logger
{
    /**
     * @var string
     */
    protected static string $file = '';

    /**
     * @return void
     */
    public static function listen(): void
    {
        if (static::$file) {
            return;
        }

        static::load();
        static::write('['.date('Y-m-d H:i:s').'] ['.Request::method().'] '.Request::fullUrl());

        DB::listen(static function ($sql) {
            foreach ($sql->bindings as $i => $binding) {
                if ($binding instanceof DateTime) {
                    $sql->bindings[$i] = $binding->format('Y-m-d H:i:s');
                } elseif (is_string($binding)) {
                    $sql->bindings[$i] = "'${binding}'";
                } elseif (is_bool($binding)) {
                    $sql->bindings[$i] = $binding ? 'true' : 'false';
                }
            }

            static::write(vsprintf(str_replace(['%', '?'], ['%%', '%s'], $sql->sql), $sql->bindings));
        });
    }

    /**
     * @return void
     */
    protected static function load(): void
    {
        static::file();

        if (is_dir($dir = dirname(static::$file)) === false) {
            mkdir($dir, 0755, true);
        }
    }

    /**
     * @return void
     */
    protected static function file(): void
    {
        $file = array_filter(explode('-', preg_replace('/[^a-z0-9]+/i', '-', Request::path())));
        $file = implode('-', array_map(static fn ($value) => substr($value, 0, 20), $file)) ?: '-';

        static::$file = storage_path('logs/query/'.date('Y-m-d').'/'.substr($file, 0, 150).'.log');
    }

    /**
     * @param string $message
     *
     * @return void
     */
    protected static function write(string $message): void
    {
        file_put_contents(static::$file, "\n\n".$message, FILE_APPEND | LOCK_EX);
    }
}
Enter fullscreen mode Exit fullscreen mode

Ahora podemos crear un ServiceProvider que gestione el debug. Lo haremos en app/Providers/Debug.php:

<?php declare(strict_types=1);

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\Database\Logger as LoggerDatabase;

class Debug extends ServiceProvider
{
    /**
     * @return void
     */
    public function boot()
    {
        $this->logging();
    }

    /**
     * @return void
     */
    protected function logging(): void
    {
        $this->loggingDatabase();
    }

    /**
     * @return void
     */
    protected function loggingDatabase(): void
    {
        if (config('logging.channels.database.enabled')) {
            LoggerDatabase::listen();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

En este mismo ServiceProvider podríamos, por ejemplo, añadir también un log que registre los envíos de correos (esto irá en un nuevo post).

Una vez tengamos el servicio, lo añadimos en config/app.php en el array de providers:

    ...

    'providers' => [
        ...

        /*
         * Application Service Providers...
         */
        App\Providers\Debug::class,
    ],
];
Enter fullscreen mode Exit fullscreen mode

También añadimos su opción de configuración en config/logging.php:

    ...

    'channels' => [
        ...

        'database' => [
            'enabled' => env('LOG_DATABASE', false),
        ],
    ],
Enter fullscreen mode Exit fullscreen mode

Y lo completamos con el valor en .env:

LOG_DATABASE=true
Enter fullscreen mode Exit fullscreen mode

De este modo nos creará una carpeta en storage/logs/query/ con cada día y dentro un fichero con el slug del path de cada petición.

El código completo lo podeis encontrar aquí https://gist.github.com/eusonlito/c0a87467b55165a9585a640f1ba4083e

Y ya sabes, si te ha parecido interesante, comparte!

Discussion

pic
Editor guide