Search by

jgcansi / pulse-php

Khyarus

Lightweight observability, monitoring and telemetry library for PHP 8.1+ and Laravel — fully local, backed by SQLite.

v1.1.0 2026-10-13 23:23 UTC

This package is auto-updated.

Last update: 2026-10-13 23:25:26 UTC


README

Banner do PulsePHP

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

Versão Testes Licença

PHP Laravel SQLite Composer PHPUnit

Ver demo Instalação Documentação Discussões

Overview do PulsePHP: sinais vitais e gráfico de requisições sobre papel de eletrocardiograma
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.

Overview no tema claro Overview no tema escuro
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.

Linha do tempo de uma requisição com spans, queries e chamadas HTTP

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.

Lista de erros agrupados Detalhe de um grupo de erro

Requests, queries e jobs

Lista de requisições com filtros
Requests: busca por URL ou rota, filtros por status, método e duração mínima.
Queries lentas e N+1
Queries: N+1 e queries mais lentas, agrupadas por formato.
Jobs de fila por classe
Jobs: execuções, falhas, média, p95 e pior caso por classe.
Chamadas HTTP externas por host
Outbound HTTP: chamadas externas por destino, com falhas e latência.
Logs ligados às requisições
Logs: cada linha ligada à requisição ou job que a escreveu.
Busca rápida com Ctrl+K
Ctrl+K: pula para qualquer tela, rota, serviço ou erro.

A tela Storage & settings mostra o tamanho do banco, as linhas por tabela, a retenção e os deploys marcados.

Instalação e execução do PulsePHP no terminal
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

Recursos do PulsePHP

  • 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 Http do Laravel e do Guzzle; queries em PHP puro via TracedPDO.
  • 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.

PHP Laravel SQLite Composer PHPUnit

⚙️ 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 web e api, 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:work não acumulam memória.
  • Logs a partir de warning (ajustável em PULSE_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 cliente Http do Laravel; para Guzzle usado diretamente, adicione o GuzzleMiddleware (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
  1. Faça um fork do projeto.
  2. Crie uma branch (git checkout -b feat/minha-feature).
  3. Rode a suíte (vendor/bin/phpunit) antes de abrir o PR.
  4. 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.

Stars


Se o PulsePHP foi útil pra você, deixe uma ⭐ — ajuda demais!