> LOADING LOG-HOLE v4.0...

LogHole

Pacote de logging para Laravel com canal Monolog para base de dados, dashboard web em /log-hole, atributo PHP 8.4 #[Loggable], comando log-hole:tail e suporte multi-driver para MySQL, MariaDB, PostgreSQL, SQLite e SQL Server.

Overview

O LogHole e um pacote de logging moderno e flexivel para Laravel com suporte multi-driver para base de dados. Integra-se directamente com a facade Log atraves de um canal Monolog personalizado, e oferece um dashboard web para navegar nos logs, um comando Artisan para acesso CLI, e o atributo PHP 8.4 #[Loggable] para logging declarativo a nivel de metodo via middleware.

PHP 8.4+ Laravel 13.x MIT License 175 Tests PHPStan Level 6 PSR-12 / Pint

Funcionalidades principais

  • Canal Monolog para base de dados -- tabela logs_hole configuravel
  • Suporte multi-driver -- MySQL, MariaDB (auto-detetada), PostgreSQL, SQLite, SQL Server
  • Dashboard web em /log-hole -- Tailwind CSS v3 + Alpine.js, dark/light mode, filtros, stats e auto-refresh
  • Comando Artisan log-hole:tail para consultar e purgar logs
  • Atributo PHP 8.4 #[Loggable] para logging declarativo a nivel de classe ou metodo
  • LogLevel enum com badges coloridos e conversao para Monolog
  • Strategy Pattern com LogDriverInterface para extender com drivers customizados
  • LIKE escaping cross-database -- clausula ESCAPE explicita; wildcards no termo de pesquisa sao sempre tratados literalmente
  • Cache de stats opcional para dashboards em tabelas com milhoes de logs
  • Purge em chunks para tabelas multi-milhao

Requisitos

O LogHole tem versoes alinhadas com cada major do Laravel. Escolha a release correspondente a sua stack:

Release PHP Laravel
4.x (atual) >= 8.4 13.x
3.x >= 8.4 11.x, 12.x
2.x >= 8.2 10.x, 11.x
1.x >= 8.2 10.x

Bases de dados suportadas: MySQL, MariaDB, PostgreSQL, SQLite e SQL Server.

Instalacao

Instale via Composer, publique a configuracao e execute a migration que cria a tabela logs_hole.

Instalacao
bash
composer require digitaldev-lx/log-hole
Publicar configuracao
bash
php artisan vendor:publish --tag="log-hole-config"
Executar migrations
bash
php artisan migrate

Configuracao

O ficheiro de configuracao publicado fica em config/log-hole.php:

config/log-hole.php
php
return [
    'database' => [
        'driver' => 'custom',
        'via' => DigitalDevLx\LogHole\Channels\DatabaseChannel::class,
        'level' => env('LOG_LEVEL', 'debug'),
        'table' => 'logs_hole',
    ],

    // Conexao a usar para os logs (null = conexao default)
    'connection' => env('LOG_HOLE_DB_CONNECTION', null),

    // Emails autorizados a aceder ao dashboard (vazio = aberto)
    'authorized_users' => [],

    // Prefixo da rota do dashboard
    'dashboard_route' => 'log-hole',

    // Logs por pagina no dashboard
    'per_page' => 25,

    // Auto-refresh do dashboard de 5 em 5 segundos
    'auto_refresh' => false,

    // TTL (segundos) para a query stats(). 0 desactiva.
    // Util quando o dashboard auto-refreshar contra uma tabela enorme.
    'stats_cache_ttl' => env('LOG_HOLE_STATS_CACHE_TTL', 0),
];

Adicionar o canal de logging

Adicione o canal database ao config/logging.php:

config/logging.php
php
'channels' => [
    // ... outros canais

    'database' => config('log-hole.database'),
],

Opcoes de utilizacao do canal

Opcao A -- definir como canal default no .env:

.env
bash
LOG_CHANNEL=database

Opcao B -- usar dentro de um stack:

config/logging.php - stack channel
php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['single', 'database'],
    ],

    'database' => config('log-hole.database'),
],

Opcao C -- usar on-demand:

On-demand
php
use Illuminate\Support\Facades\Log;

Log::channel('database')->info('Esta vai para a base de dados');

Conexao de base de dados separada

Para guardar logs noutra base de dados, defina LOG_HOLE_DB_CONNECTION. A conexao tem de existir em config/database.php e a migration deve ser executada nessa conexao.

.env
bash
LOG_HOLE_DB_CONNECTION=mysql_logs

Cache de stats no dashboard

Em tabelas com milhoes de linhas, a query de COUNT(*) por nivel que alimenta a barra de stats e cara. Com auto-refresh ligado, corre de 5 em 5 segundos por visitante. Ative cache positivo via TTL:

.env
bash
LOG_HOLE_STATS_CACHE_TTL=5

O driver usa o cache store default do Laravel sob a chave log-hole:stats:{connection}:{table}.

Utilizacao via Log facade

Use a facade Log standard do Laravel. Todos os niveis PSR-3 sao suportados: emergency, alert, critical, error, warning, notice, info, debug.

Log facade
php
use Illuminate\Support\Facades\Log;

// Se 'database' for o canal default
Log::info('Utilizador autenticou', ['user_id' => 1]);
Log::error('Falha no pagamento', ['order_id' => 42, 'reason' => 'timeout']);
Log::warning('Espaco em disco a esgotar');

// Ou apontar explicitamente ao canal database
Log::channel('database')->debug('Info de debug', ['context' => 'value']);

Atributo #[Loggable]

O LogHole expoe o atributo #[Loggable] para logging declarativo em metodos de controllers e classes inteiras. Quando o middleware LogHoleMiddleware esta activo, as accoes anotadas sao registadas automaticamente apos o request ser processado.

Configurar middleware (Laravel 11+)

bootstrap/app.php
php
use DigitalDevLx\LogHole\Middlewares\LogHoleMiddleware;

return Application::configure(basePath: dirname(__DIR__))
    // ...
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(LogHoleMiddleware::class);
    })
    ->create();

Setups antigos (Laravel 10.x com app/Http/Kernel.php) sao suportados pelo LogHole 2.x. As versoes 3 e 4 esperam o bootstrap de middleware do Laravel 11+.

Atributo a nivel de metodo

Logging por metodo
php
use DigitalDevLx\LogHole\Attributes\Loggable;

class OrderController extends Controller
{
    #[Loggable(message: 'Encomenda criada', level: 'info')]
    public function store(Request $request)
    {
        // ... logica
    }

    #[Loggable(message: 'Encomenda eliminada', level: 'warning')]
    public function destroy(Order $order)
    {
        // ... logica
    }
}

Atributo a nivel de classe

Aplicado a classe, todas as accoes do controller sao registadas automaticamente. Atributos de metodo tem prioridade sobre os de classe.

Logging de classe inteira
php
use DigitalDevLx\LogHole\Attributes\Loggable;

#[Loggable(level: 'info')]
class UserController extends Controller
{
    public function index() { /* registado automaticamente */ }
    public function show(User $user) { /* registado automaticamente */ }
}

Parametros do atributo

Parametro Tipo Default Descricao
message string '' Mensagem custom; default "{method} was called"
level LogLevel|string LogLevel::Info Nivel do log -- enum ou string ('error', 'warning'...)
includeRequest bool false Incluir metodo HTTP, URL e IP no contexto
channel ?string null Canal de log especifico (null = default)

Usar o LogLevel enum

LogLevel enum
php
use DigitalDevLx\LogHole\Attributes\Loggable;
use DigitalDevLx\LogHole\Enums\LogLevel;

#[Loggable(
    message: 'Acao critica executada',
    level: LogLevel::Critical,
    includeRequest: true,
)]
public function dangerousAction()
{
    // ...
}

Dashboard Web

O LogHole inclui um dashboard web completo, acessivel em /log-hole (configuravel via dashboard_route). Construido com Tailwind CSS v3 e Alpine.js.

Funcionalidades

  • Barra de stats -- total e contadores por nivel com badges coloridos
  • Filtros server-side -- por nivel, termo de pesquisa e intervalo de datas (from / to)
  • Tabela de logs com level badges, mensagens truncadas com tooltip, contexto JSON expandivel e timestamps relativos
  • Dark/light mode com persistencia via localStorage
  • Auto-refresh toggle (recarrega de 5 em 5 segundos)
  • Paginacao Tailwind com ordenacao estavel atraves de fronteiras de pagina

Restringir acesso por email

Por defeito o dashboard e aberto. Para restringir, adicione emails autorizados a config:

config/log-hole.php
php
'authorized_users' => [
    'admin@empresa.pt',
    'developer@empresa.pt',
],

Quando a lista nao esta vazia, apenas utilizadores autenticados com emails na lista tem acesso. Os restantes recebem 403.

Gate viewLogHole

O LogHole tambem regista um Gate viewLogHole que pode usar na sua propria logica de autorizacao:

Gate authorization
php
if (Gate::allows('viewLogHole')) {
    // utilizador tem acesso
}

Artisan -- log-hole:tail

O comando log-hole:tail permite consultar e purgar logs directamente no terminal, com output tabular e contexto em JSON formatado.

Exemplos
bash
# Ultimos 10 logs (default)
php artisan log-hole:tail

# Filtrar por nivel
php artisan log-hole:tail --error
php artisan log-hole:tail --critical
php artisan log-hole:tail --warning

# Filtrar por intervalo de datas
php artisan log-hole:tail --error --from=2024-10-01 --to=2024-10-31

# Limitar resultados
php artisan log-hole:tail --critical --take=5

# Purgar todos os logs (com confirmacao)
php artisan log-hole:tail --purge

Opcoes disponiveis

Opcao Descricao
--emergencyFiltrar por nivel EMERGENCY
--alertFiltrar por nivel ALERT
--criticalFiltrar por nivel CRITICAL
--errorFiltrar por nivel ERROR
--warningFiltrar por nivel WARNING
--noticeFiltrar por nivel NOTICE
--infoFiltrar por nivel INFO
--debugFiltrar por nivel DEBUG
--from=Data inicial (e.g. 2024-10-01)
--to=Data final (e.g. 2024-10-31)
--take=Limitar entradas (default 10, clamped 1-1000)
--purgePurgar todos os logs (com confirmacao)

Sem opcao de nivel, todos os niveis sao retornados. O contexto e mostrado em JSON pretty-printed para legibilidade.

Arquitetura de Drivers

O LogHole usa o Strategy Pattern para acesso a base de dados. Todos os drivers implementam a interface LogDriverInterface. O DriverFactory deteta automaticamente o driver da conexao e retorna a implementacao adequada.

Base de Dados Driver Pesquisa de contexto
MySQL MySqlDriver CAST(context AS CHAR) LIKE ? ESCAPE ?
MariaDB MariaDbDriver Herda a estrategia MySQL; auto-detetado via PDO version string ou pelo driver name mariadb
PostgreSQL PostgreSqlDriver context::text ILIKE ? ESCAPE ?
SQLite SqliteDriver IFNULL(context, '') LIKE ? ESCAPE ?
SQL Server SqlServerDriver CAST(context AS NVARCHAR(MAX)) LIKE ? ESCAPE ?

Todos os drivers usam ~ como caracter de escape do LIKE -- escolhido pela portabilidade entre MySQL, Postgres, SQLite e SQL Server. A semantica de pesquisa do utilizador nao muda: %, _ e ~ escritos na caixa de pesquisa sao tratados literalmente.

Usar o driver directamente

O driver e registado como singleton no service container. Pode resolve-lo directamente:

LogDriverInterface
php
use DigitalDevLx\LogHole\Drivers\Contracts\LogDriverInterface;
use DigitalDevLx\LogHole\Enums\LogLevel;

$driver = app(LogDriverInterface::class);

// Inserir um log
$driver->insert(LogLevel::Info, 'Hello', ['key' => 'value'], now());

// Consultar com filtros
$logs = $driver->query(level: LogLevel::Error, search: 'payment', limit: 20);

// Resultados paginados
$paginated = $driver->paginate(level: LogLevel::Warning, perPage: 25);

// Stats (cached quando log-hole.stats_cache_ttl > 0)
$stats = $driver->stats();
echo $stats->total;
echo $stats->countForLevel(LogLevel::Error);

// Purgar logs
$driver->purge();                                          // todos
$driver->purge(level: LogLevel::Debug);                    // por nivel
$driver->purge(before: now()->subMonth());                 // por data
$driver->purge(before: now()->subYear(), chunkSize: 5000); // delete batched

O argumento chunkSize em purge() permite eliminar em lotes em vez de num so statement -- recomendado em tabelas multi-milhao para reduzir contention de locks e volume de binlog. Passe 0 (default) para um unico DELETE.

Tabela de logs

A migration cria a tabela logs_hole (configuravel via config('log-hole.database.table')) com a seguinte estrutura:

Coluna Tipo Notas
idbigintPrimary key, auto-increment
messagetextMensagem do log
levelstringNivel do log (e.g. ERROR)
contextjsonNullable; contexto adicional
logged_atdatetimeNullable; quando o log foi criado

Indexes: logged_at e composto (level, logged_at) -- que tambem cobre filtros so-por-nivel via leftmost-prefix.

Novidades em v4.0

  • Suporte a Laravel 13 -- Pest 4, Orchestra Testbench 11.1, Larastan 3.9.
  • Bug fix: PostgreSQL e SQL Server passam a emitir clausula ESCAPE -- wildcards na pesquisa deixam de ser ignorados.
  • Bug fix: MySQL/MariaDB usam CAST(context AS CHAR) LIKE ? ESCAPE ? em vez de JSON_SEARCH, dando semantica identica em message e context.
  • Bug fix: Paginacao agora estavel quando varias linhas partilham o mesmo logged_at (adicionado tiebreaker por id).
  • Performance: DatabaseChannel resolve o driver do singleton em cada write; DriverFactory::isMariaDb() com cache por conexao.
  • Performance: opcao stats_cache_ttl para cachear a query de stats do dashboard.
  • Robustez: purge() em chunks; insert() faz fallback para now() quando loggedAt e null; fallback de error_log rate-limited.
  • Tests: 175 testes em PHPStan level 6, mais uma suite de integracao separada que corre contra Postgres 16 e MySQL 8.4 reais em CI.

Guia de upgrade

v3.x → v4.0

v4 e uma release Laravel 13 sem breaks de API publica. A camada de drivers foi reformulada internamente para corrigir bugs de escape de wildcards LIKE em PostgreSQL e SQL Server, e para unificar a semantica de pesquisa entre MySQL/MariaDB. Se so usou a facade Log, o dashboard, o comando log-hole:tail ou #[Loggable], o upgrade e directo:

Composer require
bash
composer require digitaldev-lx/log-hole:^4.0

Se implementou um driver custom contra LogDriverInterface, note que purge() aceita agora um terceiro argumento opcional int $chunkSize = 0. Callers existentes continuam a funcionar -- o novo argumento tem default backwards-compatible.

v1.x → v2.0

  1. Estrutura do config mudou -- republique o ficheiro:
    Republish config
    bash
    php artisan vendor:publish --tag="log-hole-config" --force
  2. Tag de publish renomeada -- de --tag=logs-config para --tag=log-hole-config (convencao Spatie).
  3. Atributo #[Loggable] -- a propriedade level e agora um enum LogLevel (strings ainda funcionam por backwards-compat). A propriedade publica $level foi removida em favor de $logLevel (readonly enum).
  4. Views e routes mudaram de localizacao -- views passaram de src/resources/views/ para resources/views/; routes passaram de src/routes/ para routes/. Se publicou views, republique-as.
  5. Indexes da migration -- volte a correr migrations para adicionar os novos indexes de performance:
    Re-migrate
    bash
    php artisan migrate

Boas Praticas

Usar contexto estruturado

Passe sempre um array de contexto com dados relevantes. Contexto estruturado facilita filtragem e analise no dashboard. Evite mensagens genericas -- inclua IDs, nomes de operacao e dados que ajudem no debug.

Escolher o nivel correcto

Use debug para troubleshooting, info para eventos normais, warning para situacoes anomalas mas recuperaveis, e error para falhas que requerem atencao.

Combinar com o stack channel

Use o canal stack do Laravel para enviar logs simultaneamente para ficheiro e base de dados, garantindo redundancia.

config/logging.php - Stack channel
php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['single', 'database'],
    ],
],

Purgar logs periodicamente em chunks

Use o driver no scheduler para purgar logs antigos e evitar que a tabela cresca indefinidamente. Em tabelas com muitos milhoes de linhas, use chunkSize para reduzir lock contention.

Purge agendado
php
// routes/console.php
use DigitalDevLx\LogHole\Drivers\Contracts\LogDriverInterface;

Schedule::call(function () {
    app(LogDriverInterface::class)->purge(
        before: now()->subDays(30),
        chunkSize: 5000,
    );
})->daily()->at('03:00');

Activar cache de stats em alto volume

Em dashboards com auto-refresh sobre tabelas grandes, defina LOG_HOLE_STATS_CACHE_TTL=5. A query COUNT(*) por nivel passa a ser servida do cache, reduzindo carga na base de dados.

Proteger o dashboard

Mantenha a lista de authorized_users actualizada. O dashboard mostra informacao sensivel -- trate-o como ferramenta interna.

Nao logar dados sensiveis

Nunca inclua passwords, tokens, numeros de cartao ou dados pessoais identificaveis no contexto dos logs. Mascare ou omita informacao sensivel.

Testes

O pacote inclui 175 testes Pest, analise estatica PHPStan level 6 e formatacao PSR-12 com Laravel Pint. Existe ainda uma suite de integracao separada que corre contra Postgres 16 e MySQL 8.4 reais em CI.

Comandos de desenvolvimento
bash
# Suite default (Pest, SQLite in-memory)
composer run test

# Testes com cobertura
composer run test-coverage

# Analise estatica (PHPStan level 6)
composer run analyse

# Formatacao (Laravel Pint, PSR-12)
composer run format

# Analyse + format combinado
composer run check

Para correr a suite de integracao contra Postgres ou MySQL real (skip por defeito), defina LOG_HOLE_INTEGRATION_DB:

Integracao Postgres
bash
LOG_HOLE_INTEGRATION_DB=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 \
DB_DATABASE=log_hole_test DB_USERNAME=postgres DB_PASSWORD=postgres \
vendor/bin/pest --testsuite=integration

$ composer require digitaldev-lx/log-hole

Logging que funciona em qualquer base de dados

Consulte o repositorio no GitHub para a documentacao completa, CHANGELOG, issues e contribuicoes.

> COOKIE_CONSENT_REQUIRED

We use essential cookies for the site to work, and analytics cookies (Google Analytics) to understand how you use it. Analytics cookies are only enabled with your consent. Privacy Policy