ahmedmerza / logscope
A fast, database-backed log viewer for Laravel applications
Requires
- php: ^8.2
- illuminate/contracts: >=10.0
- illuminate/database: >=10.0
- illuminate/support: >=10.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
- dev-master
- v1.5.7
- v1.5.6
- v1.5.5
- v1.5.4
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.1
- v1.3.0
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.1
- v1.1.0
- v1.0.0
- v0.6.1
- v0.6.0
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.0
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.0
- dev-fix/deprecation-message-pattern
- dev-fix/quick-filter-timezone
- dev-feat/in-ui-failure-banner
- dev-fix/early-terminate-callback
- dev-fix/channel-attribution-octane
- dev-fix/prepend-context-middleware
- dev-fix/reentrant-message-logged-guard
- dev-fix/observability-on-write-failures
- dev-fix/early-listener-registration
- dev-fix/filter-false-positives
- dev-feature/terminal-noir-redesign
This package is auto-updated.
Last update: 2026-04-30 08:59:23 UTC
README
A beautiful, database-backed log viewer for Laravel applications. Production-ready.
Quick Start
composer require ahmedmerza/logscope php artisan logscope:install php artisan migrate
Visit /logscope in your browser. That's it!
What's New
Latest: v1.5.7 β Hotfix: PHP deprecations filter regression in v1.5.5βv1.5.6. Upgrade if you rely on LOGSCOPE_IGNORE_DEPRECATIONS.
See CHANGELOG.md for full release history and behavior-change notes.
Table of Contents
- Features
- Requirements
- When to Use LogScope
- Installation
- Configuration
- Usage
- Production Deployment
- Customization
- Contributing
- License
β¨ Features
| Feature | Description |
|---|---|
| Zero-Config Capture | Automatically captures ALL logs from ALL channels |
| Request Context | Trace ID, user ID, IP, URL, and user agent for every log |
| Advanced Search | Search syntax (field:value), regex support, NOT toggle |
| Smart Filters | Include/exclude by level, channel, HTTP method, date range |
| Active Filters Bar | See all active filters at a glance, clear individually |
| Channel Search | Search and filter channels when you have many |
| JSON Viewer | Syntax-highlighted, collapsible JSON with copy support |
| Smart Context | Auto-expand Request/Model objects, redact sensitive data |
| Status Workflow | Track logs as open, investigating, resolved, or ignored |
| Log Notes | Add investigation notes to any log entry |
| Quick Filters | One-click filters for common queries |
| Keyboard Shortcuts | 14 shortcuts for navigation, status changes, and actions |
| Dark Mode | Full dark mode support with persistence |
| Shareable URLs | Current filters reflected in URL for sharing |
| Deep Linking | Link directly to specific log entries |
| Performance | Keyset pagination, batch writes, query optimization, proper indexing |
π Requirements
- PHP 8.2+
- Laravel 10+
- SQLite, MySQL, or PostgreSQL
π€ When to Use LogScope
LogScope stores logs in your database - a deliberate choice that works great for most Laravel apps.
Great fit if you:
- Want log visibility without external services
- Have a typical Laravel app (up to ~100K requests/day)
- Need rich search and filtering
- Prefer simplicity over infrastructure complexity
Consider alternatives if you:
- Process millions of requests daily
- Need months/years of log retention
- Already use centralized logging (Datadog, CloudWatch, ELK)
How LogScope handles common concerns:
| Concern | Solution |
|---|---|
| Database bloat | Retention policies with scheduled pruning (default: 30 days) |
| Performance | Batch mode writes logs after response is sent |
| Query speed | Proper indexes on common filter combinations |
β οΈ Known Limitations
LogScope captures everything that flows through Laravel's logger (Illuminate\Log\Logger). A few categories of logs structurally bypass that path and cannot be auto-captured. These are PHP / framework limitations, not bugs we can fix from a Composer package.
1. PHP's native error_log() is not interceptable
error_log("...") is a built-in PHP function that writes directly to the destination configured in php.ini's error_log directive (file, syslog, or stderr). It calls into PHP's C-level logging, never invokes set_error_handler, and there's no userland API to redirect it. Workarounds (the uopz extension, php.ini overrides, stderr capture) all live outside the PHP application β out of scope for a package.
If your app or a dependency calls error_log(), those messages land in your php-fpm/server log, not in LogScope. Search both places when investigating.
2. trigger_error(E_USER_*) should work, but depends on Laravel's exception handler
PHP routes trigger_error() through the registered set_error_handler, and Laravel's HandleExceptions bootstrapper installs one that converts these to ErrorException and reports them β which then fires MessageLogged and reaches LogScope. So in normal HTTP/CLI flow this works.
It can break in specific contexts where Laravel's exception handler is bypassed:
php artisan tinker(skips reporting viarunningUnitTests()-style guards in some versions)- Custom
set_error_handlercalls in user code that don't chain to the previous handler error_reportingbeing lowered to excludeE_USER_*levels
If something seems missing here, check error_reporting() and that \Illuminate\Foundation\Bootstrap\HandleExceptions::class is in your bootstrap chain (it is by default in Laravel 10/11/12).
3. Direct Monolog instances bypass capture
If a dependency (or your own code) does:
$logger = new \Monolog\Logger('foo'); $logger->error('something');
β¦that's a raw Monolog logger, not Laravel's Illuminate\Log\Logger. Only Laravel's logger fires MessageLogged. The Monolog instance has no way to know LogScope exists.
Opt-in workaround: push our handler onto the Monolog instance:
use LogScope\Logging\LogScopeHandler; $logger = new \Monolog\Logger('foo'); $logger->pushHandler(new LogScopeHandler('foo')); // 'foo' = the channel name to record $logger->error('captured by LogScope now');
Auto-instrumentation isn't possible β we'd have to patch the Monolog\Logger class itself.
4. SIGKILL / segfault / E_PARSE in batch write mode loses the buffered batch
Batch mode (LOGSCOPE_WRITE_MODE=batch, default) accumulates logs during the request and flushes them on app->terminating() or PHP shutdown. Both safety nets require a graceful shutdown:
kill -9(SIGKILL): cannot be caught by PHP β buffer is gone- OOM kill: same
E_PARSE/E_COMPILE_ERROR: PHP can't run user code at shutdown for these
If low-loss is critical, set LOGSCOPE_WRITE_MODE=sync to write every log immediately. Cost: each Log::*() call adds a synchronous DB round-trip.
5. null_channel filter β read before enabling
LOGSCOPE_IGNORE_NULL_CHANNEL=true drops logs that LogScope can't attribute to a named channel. This includes Laravel's own framework-level error reporter in some configurations, which means enabling this flag may silently drop unhandled exceptions. Only enable if you know exactly which no-channel logs flow through your app.
π¦ Installation
composer require ahmedmerza/logscope
Run the install command:
php artisan logscope:install php artisan migrate
Access the dashboard at /logscope.
βοΈ Configuration
After installation, configure LogScope in config/logscope.php or via environment variables.
Capture Mode
# 'all' (default) - Capture all logs automatically # 'channel' - Only capture logs sent to the logscope channel LOGSCOPE_CAPTURE=all
Write Mode (Performance)
# 'batch' (default) - Buffer logs, write after response # 'sync' - Write immediately (simple, but slower) # 'queue' - Queue each log entry (best for high-traffic) LOGSCOPE_WRITE_MODE=batch # Queue settings (when using 'queue' mode) LOGSCOPE_QUEUE=default LOGSCOPE_QUEUE_CONNECTION=
Retention
LOGSCOPE_RETENTION_ENABLED=true LOGSCOPE_RETENTION_DAYS=30
Note: Retention requires scheduling
logscope:prune- see Schedule Pruning.
Features
LOGSCOPE_FEATURE_STATUS=true # Enable status workflow LOGSCOPE_FEATURE_NOTES=true # Add notes to logs
Noise Reduction
# Filter out noisy logs LOGSCOPE_IGNORE_DEPRECATIONS=true # Skip "is deprecated" messages (default: true) LOGSCOPE_IGNORE_NULL_CHANNEL=false # Skip logs without a channel (default: false)
Note:
null_channeldefaults tofalsebecauseLog::build()(dynamic loggers) produce logs without a channel. Setting this totruewould filter out those logs.
Cache TTL
# How long (in seconds) to cache stats and filter options (levels, channels, etc.) # Set to 0 to disable caching LOGSCOPE_CACHE_TTL=60
JSON Viewer
Configure collapsible JSON behavior in config/logscope.php:
'json_viewer' => [ 'collapse_threshold' => 5, // Auto-collapse arrays/objects larger than this 'auto_collapse_keys' => ['trace', 'stack_trace', 'stacktrace', 'backtrace'], ],
Routes
LOGSCOPE_ROUTES_ENABLED=true LOGSCOPE_ROUTE_PREFIX=logscope LOGSCOPE_DOMAIN= LOGSCOPE_FORBIDDEN_REDIRECT=/ LOGSCOPE_UNAUTHENTICATED_REDIRECT=/login
Add middleware and configure error redirects:
'routes' => [ 'middleware' => ['web', 'auth'], 'forbidden_redirect' => '/', // Where to redirect on 403 (access denied) 'unauthenticated_redirect' => '/login', // Where to redirect on 401/419 (session expired) ],
Error Handling
LogScope handles errors gracefully with toast notifications:
| Error | Behavior |
|---|---|
| 401/419 (Session expired) | Toast + redirect to unauthenticated_redirect |
| 403 (Access denied) | Toast + redirect to forbidden_redirect |
| 429 (Rate limited) | Toast only (retry later) |
| 500+ (Server error) | Toast only (temporary issue) |
| Network error | Toast only (check connection) |
Note: Redirect URLs can be relative paths (
/login) or absolute URLs (https://auth.example.com/login).
π Usage
Automatic Capture
All logs are captured automatically - no code changes needed:
Log::info('User logged in', ['user_id' => 1]); Log::channel('slack')->error('Payment failed'); Log::stack(['daily', 'slack'])->warning('Low inventory');
Keyboard Shortcuts
| Key | Action |
|---|---|
j / k |
Navigate down / up |
h / l |
Previous / next page |
r |
Refresh data |
Enter |
Open detail panel |
Esc |
Close panel |
/ |
Focus search |
y |
Copy context (yank) |
n |
Focus note field |
c |
Clear all filters |
d |
Toggle dark mode |
? |
Show keyboard help |
Status shortcuts (require Shift, filter by status):
| O | Open | I | Investigating | R | Resolved | X | Ignored |
Action shortcuts (r, h, l) and status shortcuts are configurable β see Keyboard Shortcuts.
Search Syntax
Type directly in the search box using field:value syntax:
| Syntax | Example | Description |
|---|---|---|
field:value |
message:error |
Search in specific field |
-field:value |
-level:debug |
Exclude matches |
field:"value" |
message:"user login" |
Quoted values with spaces |
text |
error |
Search in all fields |
-text |
-deprecated |
Exclude from all fields |
Searchable fields: message, source, context, level, channel, user_id, ip_address, url, trace_id, http_method
Tip: Request context filters (trace ID, user ID, IP, URL) support partial matching. Type
192.168to find all IPs starting with that prefix, or42to find user IDs containing "42".
Tip: Click on trace ID, user ID, or IP address in the detail panel to pivot your investigation β severity, channel, status, and search filters are cleared so nothing is hidden. Your date range is preserved.
Examples:
# Find errors in the API channel channel:api level:error # Find payment logs excluding debug channel:payment -level:debug # Find logs mentioning a specific user ID user_id:123 # Find logs containing "timeout" anywhere timeout # Find logs with "connection failed" in message message:"connection failed" # Find context containing a job ID context:abc123 # Exclude deprecated warnings -message:deprecated # Combine multiple conditions level:error channel:database message:timeout # Find all POST requests http_method:POST # Find logs from specific URL path url:/api/payments # Exclude health check endpoints -url:/health -url:/ping # Find logs from specific IP range ip_address:192.168 # Track a specific request by trace ID trace_id:abc-123-def
Regex mode: Click the .* button to enable regex patterns:
# Match error OR warning levels level:error|warning # Match any payment-related message message:payment.*failed # Match IP addresses starting with 192.168 ip_address:192\.168\.\d+\.\d+
Both search syntax and regex can be disabled in config if not needed.
Status Workflow
Logs have a status workflow: Open β Investigating β Resolved or Ignored.
Customize who changed the status:
// In AppServiceProvider::boot() use LogScope\LogScope; LogScope::statusChangedBy(function ($request) { return $request->user()?->name; });
Customize Statuses
Override built-in statuses or add new ones in config/logscope.php:
'statuses' => [ // Override built-in status 'investigating' => [ 'label' => 'In Progress', 'color' => 'blue', ], // Add custom statuses 'waiting' => [ 'label' => 'Waiting for Customer', 'color' => 'orange', 'closed' => false, // Shows in "Needs Attention" ], 'duplicate' => [ 'label' => 'Duplicate', 'color' => 'purple', 'closed' => true, // Hidden from "Needs Attention" ], ],
Available colors: gray, yellow, green, slate, blue, red, orange, purple
Quick Filters
Configure one-click filters in config/logscope.php:
'quick_filters' => [ ['label' => 'Today', 'icon' => 'calendar', 'from' => 'today'], ['label' => 'Recent Errors', 'icon' => 'alert', 'levels' => ['error', 'critical'], 'from' => '-24 hours'], ['label' => 'Needs Attention', 'icon' => 'filter', 'statuses' => ['open', 'investigating']], ['label' => 'Resolved Today', 'icon' => 'filter', 'statuses' => ['resolved'], 'from' => 'today'], ],
Available options: label, icon (calendar/clock/alert/filter), levels, statuses, from, to
Status Shortcuts
Each status has a default keyboard shortcut (uppercase, requires Shift). Customize in config/logscope.php:
'statuses' => [ // Disable a shortcut 'ignored' => ['shortcut' => null], // Custom status with shortcut 'waiting' => ['label' => 'Waiting', 'color' => 'orange', 'shortcut' => 'w'], ],
Keyboard Shortcuts
Action shortcuts (refresh, pagination) are configurable or can be disabled:
'keyboard_shortcuts' => [ 'refresh' => 'r', // Refresh logs and stats 'prev_page' => 'h', // Previous page 'next_page' => 'l', // Next page ],
Set any shortcut to null to disable it:
'keyboard_shortcuts' => [ 'prev_page' => null, // Disable previous page shortcut 'next_page' => null, ],
Authorization
LogScope uses a flexible auth system (checked in order):
1. Custom Callback:
LogScope::auth(fn ($request) => $request->user()?->isAdmin());
2. Gate:
Gate::define('viewLogScope', fn ($user) => $user->hasRole('admin'));
3. Default: Only accessible in local environment.
Custom Context
Add custom data to every log entry (e.g., API token ID, tenant ID):
// In AppServiceProvider::boot() use LogScope\LogScope; LogScope::captureContext(function ($request) { return [ 'token_id' => $request->user()?->currentAccessToken()?->id, 'tenant_id' => $request->user()?->tenant_id, ]; });
This data is merged into the log's context field and appears in the JSON viewer.
Artisan Commands
# Import existing log files (one-time migration) php artisan logscope:import php artisan logscope:import storage/logs/laravel.log --days=7 # Prune old logs php artisan logscope:prune php artisan logscope:prune --dry-run php artisan logscope:prune --days=14
Note: The import command is a one-time migration for existing log files. After setup, new logs are captured automatically.
π Production Deployment
Recommended Settings
LOGSCOPE_WRITE_MODE=batch LOGSCOPE_RETENTION_DAYS=14 LOG_LEVEL=info
Schedule Pruning
// Laravel 11+ (routes/console.php) Schedule::command('logscope:prune')->daily(); // Laravel 10 (app/Console/Kernel.php) $schedule->command('logscope:prune')->daily();
High-Traffic Apps
For thousands of requests/day:
-
Use queue mode with a dedicated queue:
LOGSCOPE_WRITE_MODE=queue LOGSCOPE_QUEUE=logs
-
Run a separate queue worker:
php artisan queue:work --queue=logs
-
Consider shorter retention (7 days).
π¨ Customization
Theme
Customize the appearance in config/logscope.php:
'theme' => [ // Primary accent color (buttons, links, selections) 'primary' => '#10b981', // Default to dark mode for new users (users can toggle and preference is saved) 'dark_mode_default' => true, // Google Fonts (set to false to use system fonts) 'fonts' => [ 'sans' => 'Outfit', // UI text 'mono' => 'JetBrains Mono', // Code/logs ], // Log level badge colors 'levels' => [ 'error' => ['bg' => '#dc2626', 'text' => '#ffffff'], 'warning' => ['bg' => '#f59e0b', 'text' => '#1f2937'], // ... other levels ], ],
Disable external fonts (use system fonts instead):
'fonts' => [ 'sans' => false, 'mono' => false, ],
Context Sanitization
LogScope automatically expands objects in your log context for better debugging:
// Request objects show useful data Log::info('API request', ['request' => $request]); // Context: { "request": { "_type": "request", "method": "POST", "url": "...", "input": {...} } } // Models and Arrayable objects are converted Log::info('User action', ['user' => $user]); // Context: { "user": { "name": "John", "email": "..." } }
Sensitive data is automatically redacted (password, token, api_key, credit_card, etc.):
Log::info('Login', ['request' => $request]); // Input: { "email": "john@example.com", "password": "[REDACTED]" }
Configure in config/logscope.php:
'context' => [ 'expand_objects' => true, // Set false to show [Object: ClassName] 'redact_sensitive' => true, // Set false to disable redaction (not recommended) 'sensitive_keys' => [], // Empty = use defaults, or provide your own list 'sensitive_headers' => [], // Empty = use defaults, or provide your own list ],
Publishing Assets
php artisan vendor:publish --tag=logscope-config php artisan vendor:publish --tag=logscope-migrations php artisan vendor:publish --tag=logscope-views
All Environment Variables
# Capture & Performance LOGSCOPE_CAPTURE=all LOGSCOPE_WRITE_MODE=batch LOGSCOPE_QUEUE=default LOGSCOPE_QUEUE_CONNECTION= # Features LOGSCOPE_FEATURE_STATUS=true LOGSCOPE_FEATURE_NOTES=true LOGSCOPE_FEATURE_SEARCH_SYNTAX=true LOGSCOPE_FEATURE_REGEX=true LOGSCOPE_IGNORE_DEPRECATIONS=true LOGSCOPE_IGNORE_NULL_CHANNEL=false # Database & Retention LOGSCOPE_TABLE=log_entries LOGSCOPE_RETENTION_ENABLED=true LOGSCOPE_RETENTION_DAYS=30 LOGSCOPE_MIGRATIONS_ENABLED=true # Routes LOGSCOPE_ROUTES_ENABLED=true LOGSCOPE_ROUTE_PREFIX=logscope LOGSCOPE_DOMAIN= LOGSCOPE_MIDDLEWARE_ENABLED=true LOGSCOPE_FORBIDDEN_REDIRECT=/ LOGSCOPE_UNAUTHENTICATED_REDIRECT=/login # Search LOGSCOPE_SEARCH_DRIVER=database # Cache LOGSCOPE_CACHE_TTL=60 # Context Sanitization LOGSCOPE_EXPAND_OBJECTS=true LOGSCOPE_REDACT_SENSITIVE=true
π‘οΈ Extensions
LogScope Guard
Block malicious IPs directly from the LogScope UI and sync the blacklist across all your environments automatically.
composer require ahmedmerza/logscope-guard php artisan guard:install
π€ Contributing
Contributions are welcome! Please open an issue or submit a pull request.
π License
MIT License. See LICENSE for details.
