jgcansi / pulse-php
Lightweight observability, monitoring and telemetry library for PHP 8.1+ and Laravel — fully local, backed by SQLite.
Package info
pkg:composer/jgcansi/pulse-php
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpunit/phpunit: ^10.5
Suggests
- illuminate/support: Required only for the optional Laravel 9+ integration.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-29 14:29:15 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
Dashboard do PulsePHP rodando localmente — veja mais prints ↓
🎬 Demonstração em Ação
Prints reais do dashboard rodando: navegação, filtros por período/serviço/rota, gráficos, widgets e detecção de erros em tempo real.
📊 Visão geral e gráficos
Gráficos ao vivo de requisições, latência e throughput por serviço e rota.
🧩 Widgets e atividade
Widgets: slow query patterns, slow spans e outbound API calls.
Lista de atividade — requisições recentes com método, status e duração.
🩺 Saúde por rota
Cartões de saúde por rota — estado saudável (esquerda) e estado de atenção (direita).
🚨 Erros e exceções
Painel de erros — exceções recentes com classe, rota e serviço.
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).
Tudo em um único SQLite: requisições, latência, queries, spans e chamadas externas.
✨ Recursos Principais
- Buffer em memória com gravação em lote no encerramento do processo.
- 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 e exceções.
- Contexto de serviço e de rota em cada requisição.
- Captura automática de chamadas do cliente
Httpdo Laravel (método, status, destino, duração). - Dashboard ao vivo com filtros por período, serviço e rota.
- 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.
Widgets — slow queries, spans e chamadas externas. |
Saúde por rota — detecção de degradação. |
🏗️ 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 | HTML + JS | Filtros, gráficos, sparklines e auto-refresh. |
| Testes | PHPUnit | Suíte automatizada em PHP 8.1, 8.2 e 8.3. |
⚙️ Instalação
Pré-requisitos
php --version # 8.1 ou superior php -m | grep -i sqlite # extensões pdo e pdo_sqlite
Instale pelo Composer
A versão atual é um pré-lançamento (
beta.1). Por isso o Composer exige que você permita explicitamente versões instáveis:
composer require jgcansi/pulse-php:^1.0@beta
Quando a versão estável 1.0.0 for publicada, o comando volta a ser simplesmente 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'); 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 — incluindo a detecção de erros:
Exceções recentes com classe, rota e serviço, capturadas automaticamente.
Como instrumentar sua aplicação
Em PHP puro, requisições web e exceções são capturadas automaticamente pelos coletores do Pulse::init(). Consultas SQL, porém, são registradas explicitamente:
Pulse::getInstance()->recordQuery( 'SELECT * FROM users WHERE id = 42', 1.25 // duração em ms );
Chamadas outbound só são interceptadas quando passam pelo cliente HTTP do Laravel; Guzzle usado diretamente não é capturado.
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. Publique a configuração
php artisan vendor:publish --tag=pulse-config
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. Pronto — a captura é automática
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; chamadas Guzzle diretas não passam por esse interceptor.
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.service_name |
default |
Nome do serviço exibido no dashboard. |
pulse.dashboard_path |
pulse |
Rota do dashboard no Laravel. |
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). |
🧪 Testes
composer install vendor/bin/phpunit
A suíte roda no GitHub Actions em PHP 8.1, 8.2 e 8.3.
........................ 24 / 24 (100%)
OK (24 tests, 166 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+
- Exportação de traces em formato OpenTelemetry
- Retenção configurável e rotação do banco
- Alertas por e-mail / webhook
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/jgcansi/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!