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.
Funcionalidades principais
- Canal Monolog para base de dados -- tabela
logs_holeconfiguravel - 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:tailpara 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
LogDriverInterfacepara extender com drivers customizados - LIKE escaping cross-database -- clausula
ESCAPEexplicita; 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.
composer require digitaldev-lx/log-hole
php artisan vendor:publish --tag="log-hole-config"
php artisan migrate
Configuracao
O ficheiro de configuracao publicado fica em config/log-hole.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:
'channels' => [
// ... outros canais
'database' => config('log-hole.database'),
],
Opcoes de utilizacao do canal
Opcao A -- definir como canal default no .env:
LOG_CHANNEL=database
Opcao B -- usar dentro de um stack:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'database'],
],
'database' => config('log-hole.database'),
],
Opcao C -- usar on-demand:
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.
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:
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.
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+)
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
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.
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
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:
'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:
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.
# 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 |
|---|---|
| --emergency | Filtrar por nivel EMERGENCY |
| --alert | Filtrar por nivel ALERT |
| --critical | Filtrar por nivel CRITICAL |
| --error | Filtrar por nivel ERROR |
| --warning | Filtrar por nivel WARNING |
| --notice | Filtrar por nivel NOTICE |
| --info | Filtrar por nivel INFO |
| --debug | Filtrar 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) |
| --purge | Purgar 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:
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 |
|---|---|---|
| id | bigint | Primary key, auto-increment |
| message | text | Mensagem do log |
| level | string | Nivel do log (e.g. ERROR) |
| context | json | Nullable; contexto adicional |
| logged_at | datetime | Nullable; 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 deJSON_SEARCH, dando semantica identica emmessageecontext. -
Bug fix: Paginacao agora estavel quando varias linhas partilham o mesmo
logged_at(adicionado tiebreaker porid). -
Performance:
DatabaseChannelresolve o driver do singleton em cada write;DriverFactory::isMariaDb()com cache por conexao. -
Performance: opcao
stats_cache_ttlpara cachear a query de stats do dashboard. -
Robustez:
purge()em chunks;insert()faz fallback paranow()quandologgedAte null; fallback deerror_lograte-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 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
-
Estrutura do config mudou -- republique o ficheiro:
Republish configbash
php artisan vendor:publish --tag="log-hole-config" --force -
Tag de publish renomeada -- de
--tag=logs-configpara--tag=log-hole-config(convencao Spatie). -
Atributo #[Loggable] -- a propriedade
levele agora um enumLogLevel(strings ainda funcionam por backwards-compat). A propriedade publica$levelfoi removida em favor de$logLevel(readonly enum). -
Views e routes mudaram de localizacao -- views passaram de
src/resources/views/pararesources/views/; routes passaram desrc/routes/pararoutes/. Se publicou views, republique-as. -
Indexes da migration -- volte a correr migrations para adicionar os novos indexes de performance:
Re-migratebash
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.
'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.
// 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.
# 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:
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.