Statamic Logbook Icon

Statamic Logbook

Statamic Logbook Main Screenshot

Statamic Logbook

Latest Release License Open Issues Last Commit

A production-ready logging and audit trail addon for Statamic.

Statamic Logbook provides a centralized place to review:

  • System logs (Laravel / Monolog)
  • User audit logs (who changed what, and when)

All inside the Statamic Control Panel, with filtering, analytics, and CSV export.


Features

System logs

  • Captures Laravel log events automatically (no manual logging.php wiring required)
  • Stores structured records in Logbook DB tables
  • Captures request context (URL, method, IP, user, request id)
  • Supports noise filtering by channel/message fragment

Audit logs

  • Captures high-signal Statamic mutation events by default
  • Stores action, subject metadata, and entry-level before/after diffs
  • Supports field-level ignore rules and value truncation
  • Supports optional broader event discovery mode

Control Panel

  • Native Statamic CP styling/components
  • Dashboard widgets (overview, trends, live pulse)
  • Utility views with filtering and CSV export
  • Widget set includes:
    • Logbook Overview (24h health cards)
    • Logbook Trends (daily stacked volume)
    • Logbook Pulse (live mixed feed + quick filters)

Widget preview

Overview cards

Logbook Overview Cards

Trends

Logbook Trends Volume

Live pulse

Logbook Live Pulse

Widget slugs (handles)

Use these widget handles when configuring dashboard widgets:

  • logbook_stats (Overview cards)
  • logbook_trends (Volume by day + 7×24 heatmap)
  • logbook_pulse (Live feed)

Control Panel walkthrough

The utility lives under Utilities → Logbook. The same page hosts three tabs: System, Audit, and Timeline. Every feature below works the same on Statamic 4, 5, and 6 — the addon ships its own stylesheet and script bundle so nothing depends on the host CP's Tailwind purge configuration.

Dashboard widgets

  • Overview (logbook_stats): four KPI cards showing total system lines, errors, audit events, and the busiest hour in the last 24 h. Each card carries a sparkline, a period-over-period delta chip (↑ +12.4%, ↓ −3%), a status pill, and — for errors — a "Last error 3h ago" chip plus a "Top error signatures · 24h" panel that groups similar errors by a normalised fingerprint.
  • Trends (logbook_trends): daily stacked bars for the last 14 days followed by a 7×24 channel heatmap so you can spot what hour of what day is loudest at a glance.
  • Pulse (logbook_pulse): live mixed feed of system + audit events with quick-filter pills (System · Audit · Errors · Warnings).

Utility page features

Filtering & search. Each tab has a sticky filter bar with date-range pickers, level / channel / action / subject dropdowns, and a full-text search on message / user / subject. Filters compose with the URL so they're shareable; empty fields are stripped so ?level=error stays clean.

Sortable columns. Click any column header to sort. sort and dir are whitelisted server-side per table.

Per-page + pagination. A footer chip-style paginator (Prev · 1 · 2 · … · Next) with a [25 | 50 | 100 | 200] per-page dropdown. The selector preserves every other query param.

Saved filter presets. A Presets ▾ button on System + Audit tabs lets you snapshot the current filter URL under a name. Presets are stored per-tab in localStorage under statamic-logbook.presets.<scope>. Opening a preset restores the full filter state.

Live tail. A pulsing toggle next to Export CSV polls a JSON endpoint every few seconds. When new rows land, the label becomes "N new · click to refresh". The poll automatically:

  • pauses when the tab is hidden (visibilitychange) or offline,
  • resumes on online / visible,
  • backs off exponentially on consecutive errors (5 s → 10 s → 20 s → 40 s, capped at 60 s with jitter),
  • relaxes toward the upper bound when the server returns no new rows for several ticks,
  • cleans up on pagehide / beforeunload.

Density toggle. Compact · Cozy · Spacious in the toolbar. This is not a font-size switch: Compact hides secondary meta rows, forces single-line truncation, and shrinks chips + the filter grid; Cozy is the default; Spacious releases the cell clamp, lets long messages wrap, and enlarges the toolbar. The preference is persisted under statamic-logbook.density and synced across devices when preferences are linked to your CP user (see User preferences below).

Cell truncation + JSON viewer. Long messages, user ids / emails, action strings and subject titles are clamped to a single line. Every row has a JSON action that opens the full record as pretty-printed JSON in a modal (copy-to-clipboard in two clicks). The full value is never lost.

Human-readable audit actions. Raw event strings like statamic.user.saved are shown as User updated via an AuditActionPresenter. On update events, the row carries an inline ribbon with a truncated "from → to" summary of the first 1–2 changed fields (e.g. title: "Old" → "New") using the existing changes column. Zero schema changes; the raw event name stays on disk so ?action=statamic.user.saved keeps working.

Unified timeline. The Timeline tab interleaves system + audit events into a single chronological rail grouped by day (Today / Yesterday / explicit dates). Filterable by type (system / audit) and severity (error / warn / info).

CSV export. The Export CSV button downloads the currently-filtered rows as a CSV respecting all filters + sort.

Keyboard shortcuts

  • / — focus the search input on the current tab.
  • g s — go to System logs.
  • g a — go to Audit logs.

Shortcuts are suppressed while typing in form fields.


User preferences

logbook:install creates a third table, logbook_user_prefs, in the logbook database (not the project database). One row per CP user, a single JSON prefs blob. The UI uses localStorage as a zero-config fallback for density / saved presets / per-page default; when the preferences table is available, a set of CP endpoints allows those values to sync across devices:

  • GET /cp/utilities/logbook/prefs — return every pref for the current user
  • GET /cp/utilities/logbook/prefs/{key} — return one pref
  • PUT /cp/utilities/logbook/prefs/{key} — set one pref (body: { "value": ... })
  • DELETE /cp/utilities/logbook/prefs/{key} — remove one pref

All four endpoints are gated by can:view logbook and fail soft — if the table is missing (pre-upgrade install) or the logbook DB is unreachable, the UI continues to use localStorage and no error is surfaced to the user. See src/Support/UserPrefsRepository.php for the storage contract and rationale for living in the logbook DB rather than the project DB (self-contained addon, clean uninstall by dropping the logbook DB, respects teams that deliberately separate logs from prod).


Compatibility

Component Supported
Statamic v4, v5, v6
Laravel 9, 10, 11, 12
PHP 8.1, 8.2, 8.3, 8.4

Statamic 3 users stay on the dedicated 1.x LTS branch.


Installation

composer require emran-alhaddad/statamic-logbook
php artisan vendor:publish --tag=logbook
php artisan logbook:install

The logbook tag publishes the config file, the CP stylesheet, and the CP script bundle. Statamic re-runs this automatically on php artisan statamic:install, so most teams only need to run it once on initial setup.


Setup (Required)

1) Configure Logbook database credentials in .env

These are required for Logbook to work:

LOGBOOK_DB_CONNECTION=mysql
LOGBOOK_DB_HOST=127.0.0.1
LOGBOOK_DB_PORT=3306
LOGBOOK_DB_DATABASE=logbook_database
LOGBOOK_DB_USERNAME=logbook_user
LOGBOOK_DB_PASSWORD=secret

Then clear config cache:

php artisan config:clear

2) Install database tables

php artisan logbook:install

Environment Variables

All variables used by the addon:

# Required DB connection
LOGBOOK_DB_CONNECTION=mysql
LOGBOOK_DB_HOST=127.0.0.1
LOGBOOK_DB_PORT=3306
LOGBOOK_DB_DATABASE=logbook_database
LOGBOOK_DB_USERNAME=logbook_user
LOGBOOK_DB_PASSWORD=secret
 
# Optional DB tuning
LOGBOOK_DB_SOCKET=
LOGBOOK_DB_CHARSET=utf8mb4
LOGBOOK_DB_COLLATION=utf8mb4_unicode_ci
 
# System logging
LOGBOOK_SYSTEM_LOGS_ENABLED=true
LOGBOOK_SYSTEM_LOGS_LEVEL=debug
LOGBOOK_SYSTEM_LOGS_BUBBLE=true
LOGBOOK_SYSTEM_LOGS_IGNORE_CHANNELS=deprecations
LOGBOOK_SYSTEM_LOGS_IGNORE_MESSAGES=Since symfony/http-foundation,Unable to create configured logger. Using emergency logger.
 
# Audit logging
LOGBOOK_AUDIT_DISCOVER_EVENTS=false
LOGBOOK_AUDIT_EXCLUDE_EVENTS=
LOGBOOK_AUDIT_IGNORE_FIELDS=updated_at,created_at,date,uri,slug
LOGBOOK_AUDIT_MAX_VALUE_LENGTH=2000
 
# Retention
LOGBOOK_RETENTION_DAYS=365
 
# Ingestion mode
LOGBOOK_INGEST_MODE=sync
LOGBOOK_SPOOL_PATH=storage/app/logbook/spool
LOGBOOK_SPOOL_MAX_MB=256
LOGBOOK_SPOOL_BACKPRESSURE=drop_oldest
 
# Addon scheduler (flush spool)
LOGBOOK_SCHEDULER_FLUSH_SPOOL_ENABLED=true
LOGBOOK_SCHEDULER_FLUSH_SPOOL_EVERY_MINUTES=60
LOGBOOK_SCHEDULER_FLUSH_SPOOL_WITHOUT_OVERLAPPING=true

Short .env example (minimal working setup)

LOGBOOK_DB_CONNECTION=mysql
LOGBOOK_DB_HOST=127.0.0.1
LOGBOOK_DB_PORT=3306
LOGBOOK_DB_DATABASE=logbook_database
LOGBOOK_DB_USERNAME=logbook_user
LOGBOOK_DB_PASSWORD=secret
 
LOGBOOK_INGEST_MODE=spool
LOGBOOK_SPOOL_PATH=storage/app/logbook/spool

Required variables

  • LOGBOOK_DB_CONNECTION
  • LOGBOOK_DB_HOST
  • LOGBOOK_DB_PORT
  • LOGBOOK_DB_DATABASE
  • LOGBOOK_DB_USERNAME
  • LOGBOOK_DB_PASSWORD

Optional variables and behavior

  • LOGBOOK_DB_SOCKET: unix socket path.
  • LOGBOOK_DB_CHARSET: DB charset (default utf8mb4).
  • LOGBOOK_DB_COLLATION: DB collation (default utf8mb4_unicode_ci).
  • LOGBOOK_SYSTEM_LOGS_ENABLED: enable/disable system log capture (default true).
  • LOGBOOK_SYSTEM_LOGS_LEVEL: minimum system level (default debug).
  • LOGBOOK_SYSTEM_LOGS_BUBBLE: Monolog bubble behavior (default true).
  • LOGBOOK_SYSTEM_LOGS_IGNORE_CHANNELS: comma-separated ignored channels.
  • LOGBOOK_SYSTEM_LOGS_IGNORE_MESSAGES: comma-separated ignored message fragments.
  • LOGBOOK_AUDIT_DISCOVER_EVENTS: when true, merges discovered Statamic events with curated defaults.
  • LOGBOOK_AUDIT_EXCLUDE_EVENTS: comma-separated audit event classes to exclude.
  • LOGBOOK_AUDIT_IGNORE_FIELDS: comma-separated fields ignored in diffs.
  • LOGBOOK_AUDIT_MAX_VALUE_LENGTH: max stored value length before truncation.
  • LOGBOOK_RETENTION_DAYS: retention period for prune command.
  • LOGBOOK_INGEST_MODE: sync (direct DB) or spool (local file spool + background flush).
  • LOGBOOK_SPOOL_PATH: spool directory path.
  • LOGBOOK_SPOOL_MAX_MB: max spool size before backpressure policy applies.
  • LOGBOOK_SPOOL_BACKPRESSURE: currently supports drop_oldest.
  • LOGBOOK_SCHEDULER_FLUSH_SPOOL_ENABLED: enable/disable addon-level scheduler for flush command (default true).
  • LOGBOOK_SCHEDULER_FLUSH_SPOOL_EVERY_MINUTES: interval (minutes) for addon-level flush scheduling (default 60).
  • LOGBOOK_SCHEDULER_FLUSH_SPOOL_WITHOUT_OVERLAPPING: apply overlap protection for scheduled flush runs (default true).

Ingestion Modes

sync mode

  • Writes system/audit rows directly to DB in request lifecycle.

spool mode

  • Writes NDJSON records to local spool files in request lifecycle.
  • Flushes spool files to DB in background via command/scheduler.
  • If enqueue fails, Logbook falls back to direct DB insert (prevents silent drops).

Spool Flush and Background Scheduling

Flush command:

php artisan logbook:flush-spool

Common usage:

php artisan logbook:flush-spool --type=all --limit=1000
php artisan logbook:flush-spool --type=system --dry-run

Command output includes:

  • queued files (before/after)
  • queued bytes (before/after)
  • failed files (before/after)
  • failure reason and failed-file destination when flush fails

Built-in addon scheduler (spool mode)

When LOGBOOK_INGEST_MODE=spool, the addon auto-registers logbook:flush-spool in Laravel Scheduler.

Default behavior:

  • Runs every 60 minutes
  • Uses withoutOverlapping() by default
  • Can be tuned via:
    • LOGBOOK_SCHEDULER_FLUSH_SPOOL_ENABLED
    • LOGBOOK_SCHEDULER_FLUSH_SPOOL_EVERY_MINUTES
    • LOGBOOK_SCHEDULER_FLUSH_SPOOL_WITHOUT_OVERLAPPING

Important:

  • This scheduler is only active in spool mode.
  • logbook:prune is not auto-scheduled by the addon.

Application-level scheduler entry (optional override)

Add to your app scheduler (routes/console.php or Console\Kernel):

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('logbook:flush-spool --type=all --limit=1000')
->everyFiveMinutes()
->withoutOverlapping();

Short scheduler example:

Schedule::command('logbook:flush-spool')->everyFiveMinutes();

OS cron (host level, required)

* * * * * cd /absolute/path/to/your-laravel-app && php artisan schedule:run >> /dev/null 2>&1

Short cron example:

* * * * * php /absolute/path/to/your-laravel-app/artisan schedule:run >> /dev/null 2>&1

Operational Commands

  • Install tables: php artisan logbook:install
  • Prune old rows: php artisan logbook:prune
  • Flush spool: php artisan logbook:flush-spool

Run maintenance from Control Panel

From Utilities -> Logbook, use the header action buttons:

  • Prune Logs: executes php artisan logbook:prune
  • Flush Spool: executes php artisan logbook:flush-spool

Each action shows a CP toast status lifecycle:

  • in-progress when started
  • done on success
  • failed on command/transport error

Implementation note: CP action requests are submitted as form-encoded POST with _token to satisfy Laravel/Statamic CSRF validation.


Quick Verification

  1. Set required DB env vars.
  2. Run php artisan config:clear.
  3. Run php artisan logbook:install.
  4. Trigger a test log:
\Log::error('logbook smoke test', ['source' => 'manual-check']);
  1. If in spool mode, run php artisan logbook:flush-spool --type=all.
  2. Confirm rows appear in CP (System Logs / Audit Logs).

Test Coverage

This repository includes a PHPUnit suite focused on regression checks for critical behavior:

  • EventMapTest — per-major event resolution, silent filtering of missing event classes, exclusion semantics
  • StatamicAuditSubscriberResolutionTest — cross-major class-not-found safety, exclude-list round-trip
  • WidgetRegistryShimTest — capability-gated shim firing only when core registration is absent, idempotency
  • Audit action normalization mapping
  • Curated audit default mode (discover_events=false)
  • Pulse widget filter listener singleton guard

Run tests:

./vendor/bin/phpunit --configuration phpunit.xml

Rebuilding the CP bundles

The addon ships pre-minified statamic-logbook.min.css and statamic-logbook.min.js in resources/dist/. To rebuild from the source files in the same directory:

npm install
npm run build

What To Do / What Not To Do

Do

  • Use a dedicated DB/schema for Logbook where possible.
  • Keep scheduler and cron configured if using spool mode.
  • Keep LOGBOOK_AUDIT_DISCOVER_EVENTS=false unless you need wider coverage.
  • Monitor failed spool files under storage/app/logbook/spool/failed/.

Do not

  • Do not commit real credentials.
  • Do not disable scheduler while using spool mode.
  • Do not point Logbook to an uncontrolled DB.
  • Do not treat audit logs as editable content.

Troubleshooting

  • Spool files are not created:
    • run php artisan config:clear
    • verify LOGBOOK_INGEST_MODE=spool
    • verify spool directory write permissions for the PHP-FPM user
  • Flush fails:
    • read the Flush error: line from command output
    • look under storage/app/logbook/spool/failed/ for a file named like 20240101_12.ndjson.20250122093015.failed — the CP UI reports only the basename on purpose, never the absolute path
    • fix the root cause, requeue the failed file back into spool/<type>/, and run php artisan logbook:flush-spool again
  • "Unknown database" on install: php artisan logbook:install auto-creates the database on MySQL/MariaDB if the configured DB user has CREATE DATABASE. If your user doesn't, create the DB manually (CREATE DATABASE logbook_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci) and re-run install.
  • CP widgets render as unstyled gray boxes on Statamic 6: run php artisan vendor:publish --tag=logbook --force to refresh the published stylesheet + script, then clear the browser cache.
  • Pulse widget filter pills ignore clicks on Statamic 6: symptom of a stale statamic-logbook.min.js publish; re-run the vendor:publish line above.
  • "Presets ▾" dropdown doesn't appear: known fix in v2.0.0 (the button now portals its menu into <body> to escape the filter toolbar's backdrop-filter containing block); upgrade to v2.0.0+.

Release and History

Known tags:

  • v2.0.0 (current) — Statamic 6 support + CP redesign + UX polish pass
  • v1.5.1
  • v1.5.0
  • v1.4.0
  • v1.3.1
  • v1.3.0
  • v1.2.0
  • v1.1.0
  • v1.0.0

Statamic 3 users continue on the 1.x LTS branch. See CHANGELOG.md for the full per-version history.

Current release (v2.0.0)

The v2 release targets three things at once:

  1. First-class Statamic 6 support. The core widget registry binding conflict that broke the dashboard on Statamic 6 is gone. Event references are string FQCNs filtered through class_exists() so missing-event-class fatals across majors never happen. Audit\EventMap ships a curated per-major event registry (majors 3–6) that returns only the event classes that exist on the running major.
  2. Self-contained CP surface. The addon ships its own stylesheet (resources/dist/statamic-logbook.min.css) and script bundle (resources/dist/statamic-logbook.min.js) registered via Statamic's $stylesheets / $scripts. Rendering is independent of the host CP's Tailwind purge configuration, and the script runs outside the Vue-compiled widget subtree so Statamic 6's DynamicHtmlRenderer can't strip it.
  3. Deep CP UX pass. Sortable columns, density toggle, saved filter presets, live tail with adaptive polling, keyboard shortcuts, unified timeline, per-page selector, chip-style paginator, error fingerprint grouping, 7×24 channel heatmap, humanised audit actions with inline "from → to" ribbons, cross-device user preferences table, and more. See CHANGELOG.md for the full list.

License

MIT License. See LICENSE.

Author

Built and maintained by Emran Alhaddad
GitHub: https://github.com/emran-alhaddad

Changelog

See CHANGELOG.md for release history.