> 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

Utilizamos cookies essenciais para o funcionamento do site e cookies analíticos (Google Analytics) para compreender como utiliza o nosso site. Os cookies analíticos só são ativados com o seu consentimento. Política de Privacidade