jgcansi / pulse-php
Lightweight observability, monitoring and telemetry library for PHP 8.1+ and Laravel — fully local, backed by SQLite.
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- guzzlehttp/guzzle: ^7.5
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^10.5 || ^11.5
Suggests
- guzzlehttp/guzzle: Record outbound calls made with Guzzle through PulsePHP\Http\GuzzleMiddleware.
- illuminate/support: Required only for the optional Laravel 9+ integration.
- monolog/monolog: Store Monolog 3 records through PulsePHP\Logging\PulseLogHandler.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-13 23:25:26 UTC
README
PulsePHP
Observabilidade, monitoramento e telemetria para PHP 8.1+ e Laravel — 100% local, em SQLite.
Sem banco externo, sem agente, sem SaaS. Um único arquivo, um dashboard completo.
Demo · Instalação · Uso · Laravel · Roadmap · Contribuição
Overview do PulsePHP rodando localmente — veja mais prints ↓
🎬 Demonstração em Ação
Prints reais do dashboard com o tráfego de exemplo (
php examples/traffic.php).
Overview
Sinais vitais do período comparados com o período anterior, gráfico de requisições e exceções sobre papel de eletrocardiograma com marcadores de deploy, rotas que precisam de atenção e erros abertos. O tema segue o sistema ou é escolhido no botão do topo.
![]() |
![]() |
| Tema claro | Tema escuro |
Trace de uma requisição
Cada requisição ou job vira um trace: spans, queries e chamadas externas posicionados na linha do tempo, mais as exceções e os logs daquela execução. No print, o N+1 (a mesma query em sequência) aparece a olho nu.
Erros
Exceções agrupadas por classe e local. Marque como resolvida ou ignorada; se voltar a acontecer depois de resolvida, reaparece como regressed. O detalhe mostra a frequência, as rotas afetadas, os eventos recentes (cada um abre o trace) e o stack trace.
![]() |
![]() |
Requests, queries e jobs
A tela Storage & settings mostra o tamanho do banco, as linhas por tabela, a retenção e os deploys marcados.
Terminal — instalação via Composer, execução e tráfego de exemplo.
🔭 Visão Geral
PulsePHP é uma biblioteca de observabilidade local para PHP 8.1+ e Laravel 9+. Ela captura requisições, exceções, queries SQL e chamadas externas, e mostra tudo em um dashboard ao vivo — usando apenas um arquivo SQLite, sem nenhuma infraestrutura externa (sem Redis, sem banco de métricas, sem serviço na nuvem).
- 🎯 Local por padrão — seus dados ficam no seu servidor, em um SQLite.
- ⚡ Leve — buffer em memória com gravação em lote, sem overhead por request.
- 🧩 Integrável — funciona standalone em PHP puro ou plugado no Laravel 9+.
- 🔐 Seguro — dashboard negado por padrão (Basic Auth + IPs + Gate).
✨ Recursos Principais
- Buffer em memória com gravação em lote, seguro para filas, Octane e Horizon.
- Normalização e catalogação de SQL para agrupar consultas equivalentes.
- Timers para medir duração e variação de memória de operações.
- Captura automática de requisições web, exceções e jobs de fila.
- Traces: request, queries, spans, chamadas externas e exceções correlacionados, com página de detalhe.
- Detecção de N+1 e exceções agrupadas por classe e local.
- Latência p95/p99 no resumo e por rota.
- Captura de chamadas do cliente
Httpdo Laravel e do Guzzle; queries em PHP puro viaTracedPDO. - Retenção configurável, sampling e lista de caminhos ignorados.
- Alertas por webhook (JSON/Slack) e e-mail.
- Dashboard com 8 telas, busca Ctrl+K, tema claro/escuro e atualização ao vivo — assets e fontes embutidos, sem CDN.
- Erros com ciclo de vida (aberto, resolvido, ignorado, regressão), logs correlacionados e marcadores de deploy.
- Segurança por padrão: dashboard negado até configurar uma política de acesso.
- Integração opcional com Laravel 9+ via auto-discovery.
- Dados sempre locais — nada sai da sua máquina (exceto os alertas que você configurar).
🏗️ Arquitetura & Tecnologias
| Camada | Tecnologia | Descrição |
|---|---|---|
| Core | PHP 8.1+ | Núcleo standalone, sem dependência de framework. |
| Integração | Laravel 9+ | Service provider com auto-discovery (opcional). |
| Dados | SQLite (pdo_sqlite) |
Armazenamento local em arquivo único. |
| Dashboard | PHP templates + Alpine.js + Chart.js | Telas renderizadas no servidor, CSS próprio e fontes IBM Plex embutidas; nenhum CDN. |
| Testes | PHPUnit + Testbench | Suíte automatizada em PHP 8.1 a 8.4, incluindo integração com Laravel. |
⚙️ Instalação
Pré-requisitos
php --version # 8.1 ou superior php -m | grep -i sqlite # extensões pdo e pdo_sqlite
Instale pelo Composer
composer require jgcansi/pulse-php
Requisitos: PHP 8.1+ com as extensões pdo, pdo_sqlite e json (esta última já vem habilitada por padrão no PHP 8+).
📖 Uso
Uso rápido (standalone)
Inicialize o Pulse uma vez no ponto de entrada da aplicação. Os helpers pulse(), pulse_metric(), pulse_start() e pulse_end() são carregados automaticamente pelo Composer:
<?php require __DIR__ . '/vendor/autoload.php'; use PulsePHP\Pulse; Pulse::init(__DIR__ . '/storage/pulse.sqlite', options: [ 'sample_rate' => 1.0, // fração de requisições gravadas 'ignore_paths' => ['/health'], // nunca gravados (* é curinga) 'retention_days' => 7, // limpeza automática de dados antigos ]); pulse('pedido.criado'); pulse_metric('valor_venda', 250.00, ['categoria' => 'eletronicos']); pulse_start('relatorio.gerar'); // Gere o relatório. pulse_end('relatorio.gerar');
Subir o Dashboard local
O repositório inclui public/dashboard.php. Configure credenciais fortes e rode o servidor embutido apenas em loopback:
# Linux / macOS export PULSE_DASHBOARD_USER=pulse-admin export PULSE_DASHBOARD_PASSWORD='use-um-segredo-forte' php -S 127.0.0.1:8000 -t public # Windows (PowerShell) $env:PULSE_DASHBOARD_USER="pulse-admin" $env:PULSE_DASHBOARD_PASSWORD="use-um-segredo-forte" php -S 127.0.0.1:8000 -t public
Acesse http://127.0.0.1:8000/dashboard.php
O exemplo restringe o acesso a 127.0.0.1 e ::1; quando as variáveis Basic Auth estão configuradas, as credenciais também são exigidas. As políticas são cumulativas. Não exponha o endpoint publicamente sem autenticação, restrições de rede e HTTPS.
Gerar tráfego de exemplo
Em outra janela do terminal, simule requisições para ver o dashboard ganhar vida (a partir da raiz do repositório):
# Contínuo (Ctrl+C para parar) php examples/traffic.php # Execução controlada — útil para demos e GIFs php examples/traffic.php --iterations=10
O script grava cada request imediatamente em
storage/database.sqlite.
Com o tráfego rodando, o dashboard ganha vida em segundos: o script também marca um deploy, grava logs de requisições lentas e simula um N+1 na rota /api/v1/orders.
Como instrumentar sua aplicação
Em PHP puro, requisições web e exceções são capturadas automaticamente pelos coletores do Pulse::init(). Cada requisição vira um trace: tudo o que ela gerar fica correlacionado e aparece na página de detalhe do dashboard.
Queries SQL — troque o PDO por TracedPDO (mesma API). Só o SQL normalizado e a duração são gravados; os valores nunca saem da aplicação:
use PulsePHP\Database\TracedPDO; $pdo = new TracedPDO('mysql:host=localhost;dbname=app', $user, $password);
Ou registre manualmente:
Pulse::getInstance()->recordQuery('SELECT * FROM users WHERE id = 42', 1.25);
Chamadas HTTP com Guzzle — adicione o middleware ao HandlerStack:
use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use PulsePHP\Http\GuzzleMiddleware; $stack = HandlerStack::create(); $stack->push(GuzzleMiddleware::create(), 'pulse'); $client = new Client(['handler' => $stack]);
Credenciais e query string são removidas da URL antes de gravar.
Workers de longa duração (loops de fila próprios, ReactPHP etc.) — marque cada unidade de trabalho e grave o buffer ao final:
$pulse = Pulse::getInstance(); $pulse->beginTrace('job:enviar-email', 'JOB'); // ... trabalho ... $pulse->writeBuffer();
Logs — grave linhas ligadas ao trace atual, ou plugue o handler no Monolog 3:
Pulse::getInstance()->recordLog('warning', 'Estoque baixo', ['sku' => 'A-1']); use Monolog\Level; use PulsePHP\Logging\PulseLogHandler; $logger->pushHandler(new PulseLogHandler(Level::Warning));
Deploys — marque cada release; ela aparece como uma linha vertical nos gráficos:
vendor/bin/pulse deploy --db=storage/pulse.sqlite --version=v1.4.0
Retenção e alertas (CLI)
O pacote instala o binário vendor/bin/pulse. Agende-o no cron:
# Apaga telemetria com mais de 7 dias (diariamente) vendor/bin/pulse prune --db=storage/pulse.sqlite --days=7 # Avalia alertas e notifica um webhook (a cada minuto) vendor/bin/pulse check --db=storage/pulse.sqlite \ --webhook=https://hooks.slack.com/services/... --format=slack \ --error-rate=5 --exceptions=10 --p95=2000
Regras: taxa de 5xx, número de exceções e latência p95 numa janela (--window, padrão 5 min), com cooldown (--cooldown, padrão 30 min). Use off para desligar uma regra e --dry-run para testar sem notificar.
Aplicações que instanciam o Dashboard diretamente devem configurar pelo menos uma política:
use PulsePHP\Dashboard\Dashboard; $dashboard = new Dashboard($databasePath); $dashboard->authorize(static fn (): bool => $currentUser->isAdmin()); $dashboard->render();
🚀 Laravel
O service provider é descoberto automaticamente pelo Composer. Nenhum passo extra de registro.
1. Instale
php artisan pulse:install
O comando publica config/pulse.php e mostra os próximos passos.
Por padrão, o Dashboard fica em /pulse e o banco em storage/pulse.sqlite (ajustável via database_path em config/pulse.php).
2. Autorize o acesso
O acesso permanece negado até você definir o Gate viewPulse:
use Illuminate\Support\Facades\Gate; Gate::define('viewPulse', static fn (User $user): bool => $user->is_admin);
Opcionalmente, combine Basic Auth e whitelist de IPs no .env:
PULSE_DASHBOARD_USER=pulse-admin PULSE_DASHBOARD_PASSWORD=use-um-segredo-forte PULSE_DASHBOARD_IPS=127.0.0.1,::1 PULSE_SERVICE_NAME=api-pagamentos
Quando configuradas, as credenciais e a whitelist são verificadas além do Gate. O arquivo publicado também permite ajustar enabled, rota, middleware e coletores.
3. Agende a manutenção
// routes/console.php (Laravel 11+; no 9/10 use o schedule() de app/Console/Kernel.php) use Illuminate\Support\Facades\Schedule; Schedule::command('pulse:prune')->daily(); Schedule::command('pulse:check')->everyMinute(); // com PULSE_ALERTS_ENABLED=true
Alertas usam PULSE_ALERT_WEBHOOK (JSON ou Slack) e/ou PULSE_ALERT_MAIL_TO (mailer do Laravel).
4. Pronto — a captura é automática
- Requisições dos grupos
webeapi, com nome da rota, sem query string. - Exceções reportadas pelo Laravel (HTTP, jobs e console). O que está em
dontReport(404, validação etc.) é ignorado. - Queries via
DB::listen, correlacionadas ao request ou job. - Jobs de fila viram traces próprios (
job:App\Jobs\...) com span de duração. - O buffer é gravado ao fim de cada request e de cada job, então Octane, Horizon e
queue:worknão acumulam memória. - Logs a partir de
warning(ajustável emPULSE_LOG_LEVEL), ligados ao request ou job. Logs que carregam uma exceção são pulados, porque a exceção já é registrada.
Marque deploys no seu script de deploy para vê-los nos gráficos:
php artisan pulse:deploy "$(git rev-parse --short HEAD)"
As chamadas feitas com Illuminate\Support\Facades\Http aparecem como span http.outbound:{host} e na tabela de chamadas externas, com método, status e duração. Credenciais e query string são removidas da URL persistida por segurança:
use Illuminate\Support\Facades\Http; $response = Http::withToken(config('services.stripe.secret')) ->get('https://api.stripe.com/v1/balance');
Sem
pulse_start()manual ao redor das chamadas — o middleware e os eventos do cliente HTTP cuidam de tudo. A captura cobre o clienteHttpdo Laravel; para Guzzle usado diretamente, adicione oGuzzleMiddleware(veja Como instrumentar).
As rotas web e API recebem contexto com o nome da rota resolvida ou seu URI. O serviço usa PULSE_SERVICE_NAME, com fallback para APP_NAME e depois default. Bancos SQLite v1 existentes recebem as novas colunas na inicialização, sem apagar os registros anteriores.
Configuração
| Variável / chave | Padrão | Descrição |
|---|---|---|
pulse.enabled |
true |
Liga/desliga toda a coleta. |
pulse.database_path |
storage/pulse.sqlite |
Caminho do banco SQLite (PULSE_DB_PATH). |
pulse.service_name |
default |
Nome do serviço exibido no dashboard. |
pulse.dashboard_path |
pulse |
Rota do dashboard no Laravel (PULSE_PATH). O endpoint ao vivo fica em {path}/data. |
pulse.dashboard_middleware |
['web'] |
Middleware aplicado à rota do dashboard. |
pulse.dashboard_user |
null |
Usuário do Basic Auth do dashboard (.env: PULSE_DASHBOARD_USER). |
pulse.dashboard_password |
null |
Senha do Basic Auth do dashboard (.env: PULSE_DASHBOARD_PASSWORD). |
pulse.dashboard_allowed_ips |
[] |
Whitelist de IPs do dashboard (.env: PULSE_DASHBOARD_IPS). |
pulse.collect_requests |
true |
Captura de requisições web. |
pulse.collect_exceptions |
true |
Captura de exceções. |
pulse.collect_queries |
true |
Captura de consultas SQL. |
pulse.collect_outbound_requests |
true |
Captura de chamadas HTTP externas (cliente Http do Laravel). |
pulse.collect_jobs |
true |
Traces e spans para jobs de fila. |
pulse.collect_logs |
true |
Grava logs do Laravel (PULSE_COLLECT_LOGS). |
pulse.log_level |
warning |
Nível mínimo de log gravado (PULSE_LOG_LEVEL). |
pulse.sample_rate |
1.0 |
Fração de requisições/jobs gravados (PULSE_SAMPLE_RATE). Exceções sempre são gravadas. |
pulse.ignore_paths |
/up,/health,... |
Caminhos nunca gravados, * é curinga (PULSE_IGNORE_PATHS). O dashboard é sempre ignorado. |
pulse.max_buffer_size |
1000 |
Registros em memória que forçam uma gravação antecipada. |
pulse.n_plus_one_threshold |
5 |
Repetições da mesma query num trace para sinalizar N+1. |
pulse.retention_days |
7 |
Dias de telemetria mantidos (PULSE_RETENTION_DAYS). |
pulse.prune_lottery |
[1, 200] |
Chance de limpar dados antigos durante uma gravação. |
pulse.alerts.* |
desligado | Regras, janela, cooldown, webhook e e-mail dos alertas (PULSE_ALERTS_ENABLED, PULSE_ALERT_*). |
🧪 Testes
composer install vendor/bin/phpunit
A suíte roda no GitHub Actions em PHP 8.1 a 8.4. Os testes de integração com Laravel (--testsuite Laravel) usam orchestra/testbench e rodam a partir do PHP 8.2.
O dashboard não tem etapa de build: o CSS e o JS ficam em src/Dashboard/resources/assets/ e são servidos pelo próprio PHP com cache.
...................................................................... 70 / 70 (100%)
OK (70 tests, 420 assertions)
🗺️ Roadmap
- Núcleo standalone com buffer e flush em lote
- Normalização e catalogação de SQL
- Dashboard ao vivo com filtros e widgets
- Integração opcional com Laravel 9+
- Retenção configurável e limpeza do banco
- Alertas por e-mail / webhook
- Traces por request/job, detecção de N+1 e percentis
- Exportação de traces em formato OpenTelemetry
- Captura de comandos Artisan agendados
Veja os issues abertos para o plano completo.
🤝 Contribuição
Contribuições são muito bem-vindas! Abra uma issue para discutir ideias ou envie um pull request.
# Fork -> branch -> commit -> pull request git clone https://github.com/Khyarus/pulse-php.git cd pulse-php composer install git checkout -b feat/minha-feature git commit -m "feat: adiciona minha feature" git push origin feat/minha-feature
- Faça um fork do projeto.
- Crie uma branch (
git checkout -b feat/minha-feature). - Rode a suíte (
vendor/bin/phpunit) antes de abrir o PR. - Abra um pull request descrevendo a mudança.
📄 Licença
Distribuído sob a Licença MIT. Veja LICENSE para detalhes.
Criado e mantido por João Gabriel Cansi Silveira.
Se o PulsePHP foi útil pra você, deixe uma ⭐ — ajuda demais!









