Log viewer & lightweight profiler for Symfony. It plugs into your existing logs β no separate store, no database, no build step. MIT licensed.
LogUI is an embeddable Composer package (in the spirit of Laravel Telescope / the Symfony Profiler) that adds a small web UI to your app to browse requests, console commands and raw log files.
For every HTTP request and console command it captures pragmatic stats (duration, RAM
startβendβpeak, SQL query count, log-level counts) plus the request's own log records, and appends it
all as one LOGUI@{json} line to a telemetry log file. The UI reads those lines back. It also
reads your raw .log files directly (Monolog, nginx access/error, β¦).
No separate store. No new database. No front-end build.
Request list: clickable stats, HTTP + console profiles, method / level / SQL columns, themes.
Request detail: timing, RAM startβpeak, SQL count, and the request's own log records (filterable by level & channel).
Health tab (sepia theme): host cards (uptime, load, memory, aggregate disk usage β red over 85%) and the per-filesystem disks table.
- Auto-capture: HTTP requests, console commands, Monolog records (level counts + the request's records), uncaught exceptions, SQL queries (count + slow, when
doctrine/dbalis present). - Pure-PHP stats (
memory_get_usage/peak+ a Doctrine middleware) β no PHP extension required. - Telemetry appended to a log file (
LOGUI@sentinel), read back by the UI β no database. Written directly (not through Monolog), so it works in prod regardless offingers_crossed/rotating_filehandlers. Reads are tail-bounded (multi-GB logs are fine). - Request detail shows that request's log records (all channels, even ones your file handlers drop), filterable by level/channel.
- Raw
.logviewer (Files tab): multi-format parser (Monolog line and JSON formatters, Symfony console logs, nginx access/error, ANSI-stripped) + auto-discovery of Monolog handler files + a scan of your log directories. - JSON-aware messages: a message that is text followed by JSON is detected and the JSON is pretty-printed, syntax-highlighted and collapsed to ~2 lines with an expand/collapse toggle (client-side, in both the request detail records and the raw log viewer).
- Dashboard UI: clickable stats (general counts), method column, filters (level/type/method/search), pagination (100/page), and dark / light / sepia themes.
- Health tab (read-only host metrics): uptime, load, memory, disks, network interfaces, top processes (
ps), optional CPU temperature/throttle (Raspberry Pi),vnstatbandwidth, Docker containers and configured systemd services. Every probe degrades gracefully β anything unavailable on the host is simply not shown. - Security: open in
dev/test; in production it is fail-closed (password, or delegated to your firewall). Sensitive context keys are redacted before writing.
- PHP 8.2+
- Symfony 6.4 / 7.x / 8.x (HttpKernel, Console, HttpFoundation, Config, DependencyInjection, Routing, EventDispatcher)
- TwigBundle (the UI is rendered with Twig) β pulled in automatically
- Monolog 3
- (optional)
doctrine/dbal^3.7|^4.0 β enables SQL query counting
LogUI ships a Flex recipe, so the quickest install is to point your app at this repo as a custom
recipe endpoint and require the package β the bundle, route and config are wired automatically, and
a random LOGUI_PASSWORD is generated into .env (override it in .env.local for production).
1. Add the recipe endpoint to your app's composer.json:
{
"extra": {
"symfony": {
"allow-contrib": true,
"endpoint": [
"https://raw.githubusercontent.com/aleblanc/logui/main/index.json",
"flex://defaults"
]
}
}
}2. Require the package β the recipe runs on install:
composer require aleblanc/loguiThis auto-registers the bundle, creates config/routes/log_ui.yaml + config/packages/log_ui.yaml,
and writes a generated LOGUI_PASSWORD to .env (a default, like APP_SECRET; for production put a
real secret in .env.local, which isn't committed).
Monolog capture is automatic β the bundle wires its handler onto every channel for you (no
monolog.yaml edit). Disable it with log_ui.capture_monolog: false (e.g. in production); see
Configuration.
Open the UI at /_logui β the default path (configurable via log_ui.ui_path). It's open in
dev/test; in prod it's fail-closed behind LOGUI_PASSWORD (or delegate to your firewall, see
Access control).
Without the recipe (or before tagging a release), do it by hand: add
Aleblanc\LogUi\Bridge\Symfony\LogUiBundle::class => ['all' => true]toconfig/bundles.php, createconfig/routes/log_ui.yamlwithresource: '@LogUiBundle/config/routes.php', and setLOGUI_PASSWORDyourself. The bundle's config alias islog_ui. Monolog capture is still automatic. Recipe details & thesymfony/recipes-contribpath:recipes/README.md.
All keys are optional with sensible defaults (config/packages/log_ui.yaml):
log_ui:
telemetry_file: '%kernel.logs_dir%/%kernel.environment%.log' # existing log to write/read telemetry
slow_query_ms: 50 # SQL slower than this is flagged
max_records_per_profile: 1000 # cap on records captured per request (bounds line size)
ui_path: /_logui
access: password # password (default) | delegate
ui_password: '%env(LOGUI_PASSWORD)%' # required in prod when access=password
ignore_paths: ['/_wdt', '/_profiler'] # never profiled (the UI path is always added)
capture_monolog: true # auto-wire the handler onto every Monolog channel
discover_monolog: true # auto-list files from Monolog handlers (Files tab)
log_dirs: ['%kernel.logs_dir%'] # directories scanned for *.log (Files tab)
external_logs: [] # extra .log files to expose (outside the dirs above)
redact_keys: [password, passwd, secret, token, authorization, api_key]
health_services: [] # systemd units shown on the Health tab (e.g. [nginx, mariadb, php8.4-fpm])In dev/test the UI is open. In prod it is fail-closed β choose how to unlock it:
access: password (default) β serves nothing unless LOGUI_PASSWORD is set (the recipe
generates one in .env; put a real, dedicated secret in .env.local for production β never reuse
APP_SECRET). ui_password defaults to %env(LOGUI_PASSWORD)%, so it works out of the box. Log in
by sending that password on the request:
# HTTP header (preferred β doesn't end up in access logs)
curl -H "X-LogUI-Password: <your-password>" https://example.com/_logui
# or query string (browser)
https://example.com/_logui?_pw=<your-password>On a successful login LogUI sets a session cookie (logui_auth, HttpOnly, derived from the
password via HMAC β never the password itself), so you only supply ?_pw= once and navigation
then works without it. The cookie lasts for the browser session and invalidates if the password
changes. For a fully integrated production console (your own login, roles), delegate is still the
cleaner choice.
access: delegate (recommended in prod) β LogUI gates nothing and trusts your own security.
You log in with your normal app session, and navigation just works. Protect the route in
security.yaml:
access_control:
- { path: ^/_logui, roles: ROLE_ADMIN }Read-only host metrics at /_logui/health. Every metric is gathered on demand and degrades
gracefully: when the source file/command is missing, that card or section is simply not rendered
(nothing to configure to "turn it off" on an unsupported host).
| Card | Shows | Source | Colour |
|---|---|---|---|
| Host | machine model (or OS family) | /proc/cpuinfo (Model:), else PHP_OS_FAMILY |
β |
| Uptime | human uptime (10j 20h 0min) |
/proc/uptime |
β |
| Temperature | CPU temp (Raspberry Pi) | vcgencmd measure_temp, else /sys/class/thermal/thermal_zone0/temp |
green <60 Β°C, amber <70 Β°C, red β₯70 Β°C |
| Load (1/5/15 min) | load averages + CPU count + bar | /proc/loadavg, /proc/cpuinfo |
green; amber > 70% of cores; red > cores |
| Memory | used %, used/total + cache, bar | /proc/meminfo |
green <70%, amber <85%, red β₯85% |
| Disk | aggregate used/size across all real filesystems, bar | sum of df |
green <70%, amber <85%, red β₯85% |
| Section | Shows | Source | Notes |
|---|---|---|---|
| Power / Throttling | under-voltage / frequency-capped / throttled / soft-temp flags (current + since-boot) | vcgencmd get_throttled |
Raspberry Pi only; "OK" when clean |
| Disks | per-filesystem: mount, source, FS, used, size, usage % + bar | df -B1 --output=source,fstype,size,used,avail,pcent,target |
skips tmpfs/devtmpfs/overlay/udev/squashfs; same green/amber/red thresholds |
| Top processes | up to 20 processes by CPU: PID, user, %CPU, %MEM, RSS, command | ps -eo pid,user,pcpu,pmem,rss,comm --sort=-pcpu |
%CPU amber β₯40, red β₯80 |
| Network interfaces | name, state, IPv4 addresses, MAC | ip -j addr show |
skips lo, veth*, br-* |
| Network usage | per-interface βrx / βtx / total, and current month total β projected estimate (+ % of month elapsed) | vnstat --json |
hidden entirely when vnstat is absent |
| Docker | container, image, status, live CPU% and memory (usage / limit) | docker ps -a + docker stats --no-stream |
CPU/memory shown only when docker stats succeeds |
| Services | configured systemd units with their state badge | systemctl is-active <unit> |
only the units listed in health_services (empty β section hidden) |
Configure the monitored systemd units in config/packages/log_ui.yaml:
log_ui:
health_services: [nginx, mariadb, php8.4-fpm]A request-scoped holder is opened on kernel.request, fed log records by a Monolog handler and
exceptions by the kernel exception event, then finalized on kernel.terminate, which appends a
single LOGUI@{json} line directly to telemetry_file (not through Monolog β so it's immune to
fingers_crossed buffering and rotating_file naming, which otherwise swallow telemetry in prod).
Console commands are handled symmetrically via ConsoleEvents. The UI reads those lines back
(TelemetryReader, tail-bounded) and renders the dashboard with Core's filtering/sorting and
Twig templates (under templates/logui/, exposed as @LogUi). SQL counting is a DBAL middleware,
registered only when Doctrine DBAL is installed.
The telemetry file is a normal append-only log. Reads are tail-bounded, but the file itself grows
over time β rotate it like any log (logrotate, or point telemetry_file at a path your existing
rotation already covers).
A single package, layered: Aleblanc\LogUi\Core\* (framework-agnostic β zero Symfony/Monolog/Doctrine
imports, enforced by a custom PHPStan rule) and Aleblanc\LogUi\Bridge\Symfony\* (the bundle wiring).
The capture/storage/query Core stays framework-agnostic; the UI is Symfony + Twig only for now β a
future logui-laravel adapter would reuse the Core and ship its own (Blade) views.
- Laravel adapter (the Core is already framework-agnostic).
- Publication on Packagist.
- Pulse-style stats dashboard (Γ la Laravel Pulse): aggregated cards over the telemetry β
slowest requests/commands, busiest endpoints, error/exception rates, SQL hotspots, RAM peaks β
computed from the existing
LOGUI@lines, no new store. - Server health & system stats: lightweight host metrics next to app telemetry β
CPU/load, RAM, disk usage/inodes, uptime β read from
/proc(orsys_getloadavg()), no agent. - Network usage via vnstat: parse
vnstat --jsonto show daily/monthly bandwidth per interface. - GoAccess integration: surface a GoAccess HTML/JSON report from the parsed access logs (the Files tab already discovers nginx access/error logs) for top URLs, visitors, status codes.
- MCP server: expose LogUI telemetry to AI agents over the Model Context Protocol
(query requests/commands/logs, fetch a profile, summarize errors) β read-only, reusing
TelemetryReader.
composer install
composer test # PHPUnit
composer stan # PHPStan (level 8)
composer cs # PHP-CS-Fixer (@Symfony)MIT Β© aleblanc