This Symfony bundle provides a user-friendly interface and API for viewing application logs (Monolog). It supports log reading through standard PHP methods as well as a high-performance Go-based parser.
Explore the user interface and features of the Log Viewer:







composer require danilovl/log-viewer-bundle
config/bundles.php (if not done automatically):return [
// ...
Danilovl\LogViewerBundle\LogViewerBundle::class => ['all' => true],
];
php bin/console assets:install
config/routes.yaml:_danilovl_log_viewer:
resource: "@LogViewerBundle/Resources/config/routing.yaml"
Create a configuration file config/packages/danilovl_log_viewer.yaml.
This example shows all available configuration keys with their structure and default values.
danilovl_log_viewer:
# Source settings
sources:
# Directories to search for .log files (default: ['%kernel.logs_dir%'])
dirs: ['%kernel.logs_dir%']
# Individual log files (default: [])
files: []
# Log files to ignore (supports filenames or full paths) (default: [])
ignore: []
# Max file size to be read in bytes (default: null, read entire file)
max_file_size: null
# Allow log file deletion from the dashboard (default: false)
# Note: Even if enabled, the file must be writable by the web server.
allow_delete: false
# Allow log file download from the dashboard (default: false)
# Note: Even if enabled, the file must be readable by the web server.
allow_download: false
# Remote hosts configuration (default: [])
remote_hosts:
- name: ~ # Required: Unique name for this remote host
type: 'ssh' # Connection type (ssh, sftp, http)
host: ~ # Required: Remote host address
port: 22
user: ~
password: ~
ssh_key: ~ # Path to the SSH private key
max_file_size: null # Max file size for this remote host
dirs: []
files: []
ignore: []
# Parser settings
parser:
# Default parser for all files (default: null)
default: null
# Parser overrides for specific files (default: [])
# Key is the absolute file path, value is the parser type.
overrides: []
# Enable Go-based parser for high performance (default: false)
go_enabled: false
# Path to the Go parser binary
go_binary_path: '%kernel.project_dir%/vendor/danilovl/log-viewer-bundle/bin/dist/go-parser'
# Cache settings
cache:
# Enable caching for auto-detected parser types (default: false)
parser_detect_enabled: false
# Enable caching for log statistics (default: true)
statistic_enabled: true
# Cache interval (e.g., "5 sec", "1 minute")
statistic_interval: '5 sec'
# Dashboard page settings
dashboard_page:
# Enable statistics for dashboard (default: false)
statistic_enabled: false
# Enable auto-refresh (default: false)
auto_refresh_enabled: false
# Auto-refresh interval (default: '1 minute')
auto_refresh_interval: '1 minute'
# Show countdown for auto-refresh (default: false)
auto_refresh_show_countdown: false
# Live log page settings
live_log_page:
# Enable live log page (default: false)
enabled: false
# Live update interval (default: '5 sec')
interval: '5 sec'
# Log levels to show in live update (default: [])
# Supported: emergency, alert, critical, error, warning, notice, info, debug
levels: []
# Override sources for live log page
sources:
dirs: []
files: []
ignore: []
remote_hosts: []
# Log detail page settings
log_page:
# Enable statistics for individual log files (default: false)
statistic_enabled: false
# Enable auto-refresh (default: false)
auto_refresh_enabled: false
# Auto-refresh interval (default: '5 sec')
auto_refresh_interval: '5 sec'
# Show countdown for auto-refresh (default: false)
auto_refresh_show_countdown: false
# Entries limit (default: 50)
limit: 50
# AI Integration settings
ai:
# Log levels for which "Ask AI" button appears on log entries (default: [])
# Supported: emergency, alert, critical, error, warning, notice, info, debug
button_levels: []
# Custom AI chats configuration (default: presets for ChatGPT, Perplexity, Gemini, Claude, DeepSeek)
chats:
- name: 'ChatGPT'
url: 'https://chatgpt.com/?q={prompt}'
has_prompt: true
- name: 'Google Search'
url: 'https://www.google.com/search?q={prompt}'
has_prompt: false
# Custom API prefix for bundle routes (default: '/danilovl/log-viewer/api')
api_prefix: '/danilovl/log-viewer/api'
# Webpack Encore build name (leave null for default)
encore_build_name: null
# Log Notification settings
notifier:
# Enable notifications (default: false)
enabled: false
# Notification rules (default: [])
rules:
- name: 'Critical Errors'
# Log levels for which this rule applies (default: [])
levels: ['critical', 'error', 'emergency']
# Keywords that the log entry must contain (default: [])
contains: ['Fatal error', 'Database connection failed']
# Notifier channels (default: [])
# Available channels: chat/slack, chat/telegram, email
channels: ['chat/slack']
A practical example for a typical production environment.
danilovl_log_viewer:
sources:
dirs:
- '%kernel.logs_dir%'
- '/var/log/nginx'
files:
- '/var/log/syslog'
allow_delete: true
allow_download: true
max_file_size: 20971520 # 20 MB
remote_hosts:
- name: 'prod_server'
type: 'ssh'
host: '1.2.3.4'
user: 'deploy'
ssh_key: '%kernel.project_dir%/config/ssh/id_rsa'
dirs: ['/var/www/app/var/log']
files: ['/var/log/php8.2-fpm.log']
ignore: ['cache.log']
parser:
go_enabled: true
overrides:
'/var/log/nginx/access.log': 'nginx_access'
'/var/log/nginx/error.log': 'nginx_error'
'/var/log/syslog': 'syslog'
dashboard_page:
statistic_enabled: true
auto_refresh_enabled: true
log_page:
statistic_enabled: true
auto_refresh_enabled: true
auto_refresh_show_countdown: true
limit: 100
live_log_page:
enabled: true
interval: '3 sec'
levels: ['error', 'critical', 'emergency']
sources:
dirs: ['%kernel.logs_dir%/important']
files: ['%kernel.logs_dir%/custom.log']
ignore: ['ignore_this.log']
remote_hosts:
- { name: 'prod-server', host: '1.2.3.4', files: ['/var/log/app.log'] }
ai:
button_levels: [error, critical, alert, emergency]
chats:
- { name: 'ChatGPT', url: 'https://chatgpt.com/?q={prompt}', has_prompt: true }
- { name: 'Perplexity', url: 'https://www.perplexity.ai/?q={prompt}', has_prompt: true }
- { name: 'Gemini', url: 'https://gemini.google.com/app?q={prompt}', has_prompt: true }
- { name: 'Claude', url: 'https://claude.ai/new?q={prompt}', has_prompt: true }
- { name: 'DeepSeek', url: 'https://chat.deepseek.com/?q={prompt}', has_prompt: true }
notifier:
enabled: true
rules:
- name: 'App Critical'
levels: ['critical', 'error', 'emergency']
channels: ['chat/slack', 'email']
Note: for ssh and sftp types, the ssh2 PHP extension is required.
To enable real-time notifications for new log entries matching your rules, you need to run the watcher command in the background.
php bin/console danilovl:log-viewer:watch --interval=5 --limit=100
--interval (-i): Polling interval in seconds (default: 5).--limit (-l): Maximum number of new entries to process per file in each poll (default: 100).The watcher uses the symfony/notifier component. Make sure you have configured your Notifier transports in your Symfony application.
To use the channels defined in your configuration, you need to configure the corresponding transports in your Symfony application's config/packages/framework.yaml.
Install the Slack notifier: composer require symfony/slack-notifier
# config/packages/framework.yaml
framework:
notifier:
chatter_transports:
slack: '%env(SLACK_DSN)%'
Channel in rule: chat/slack
Install the Telegram notifier: composer require symfony/telegram-notifier
# config/packages/framework.yaml
framework:
notifier:
chatter_transports:
telegram: '%env(TELEGRAM_DSN)%'
Channel in rule: chat/telegram
Make sure the mailer component is configured:
# config/packages/framework.yaml
framework:
notifier:
texter_transports:
# If you want to use texter for email/sms
channel_policy:
email: ['email']
Note: For email notifications to work through the Notifier, you might also need to set up a channel_policy or ensure your AdminNotification logic (if customized) supports it. By default, the bundle sends a standard Notification with the specified channels.
Refer to the official Symfony Notifier documentation for detailed transport configuration (DSN) for each provider.
The following parsers are available by default:
monolog - Standard Monolog format (usually auto-detected for Symfony logs).json - Logs in JSON format (one object per line).nginx - Nginx access logs.nginx_error - Nginx error logs.apache - Apache access logs.php - PHP error logs.mysql - MySQL error logs.syslog - Traditional Syslog.syslog-modern - Modern Syslog format.doctrine - Doctrine DBAL logs.access - Generic access logs.supervisord - Supervisord logs.The bundle includes a curated list of popular regular expression templates to help you find common issues quickly. Each template provides optimized patterns for both PHP and Go (if the Go parser is enabled).
Available template categories:
You can select these templates from the dropdown menu in the search bar.
After installation and route configuration, the dashboard will be available at: /danilovl/log-viewer
The bundle's assets are pre-built and included in the package. If you want to customize the frontend:
docker compose run --rm node npm install
docker compose run --rm node npm run build
To clear the bundle cache (including log statistics and parser detection), run the following command:
php bin/console danilovl:log-viewer:cache-clear
All API endpoints are prefixed with /danilovl/log-viewer/api.
GET /config
Returns the current bundle configuration (available levels, channels, etc.).
GET /structure
Returns a hierarchical structure of folders and log files.
GET /folders
Returns a flat list of folders containing log files with their total count.
Response: { "folders": [...], "totalCount": 0 }
GET /entries
Returns log entries for a specific source with pagination.
Query parameters:
sourceId (string, required): The Source ID to view.limit (int): Number of entries (default: 50).cursor (string): Cursor for pagination.offset (int): Numeric offset.sortDir (desc|asc): Sorting direction.level (string): Filter by log level.channel (string): Filter by channel.search (string): Search text.searchRegex (bool): Enable regex search.searchCaseSensitive (bool): Enable case-sensitive search.dateFrom (string): Filter by start date.dateTo (string): Filter by end date.GET /file-content
Returns raw file content with pagination and line jumping.
Query parameters:
sourceId (string, required): The Source ID.limit (int): Number of lines (default: 100).offset (int): Numeric offset (starting line).line (int): Jump to a specific line number.GET /entries/new
Returns only new log entries across specified sources (used for real-time updates).
Query parameters:
levels (string): Comma-separated list of log levels to filter (optional).sourceIds (string): Comma-separated list of Source IDs (optional).GET /global-search
Returns log entries for multiple sources with combined sorting.
Query parameters:
sourceId (string, required): Comma-separated list of Source IDs.limit (int): Number of entries.offset (int): Numeric offset.sortDir (desc|asc): Sorting direction.level (string): Filter by log level.channel (string): Filter by channel.search (string): Search text.searchRegex (bool): Enable regex search.searchCaseSensitive (bool): Enable case-sensitive search.GET /entries-count
Returns the total count of entries for the given filters.
Query parameters:
sourceId (string, required): The Source ID.level (string): Filter by log level.channel (string): Filter by channel.search (string): Search text.searchRegex (bool): Enable regex search.searchCaseSensitive (bool): Enable case-sensitive search.dateFrom (string): Filter by start date.dateTo (string): Filter by end date.Response: { "totalCount": 0 }
GET /stats
Returns statistics for a specific file (total count, distribution by levels and channels).
Query parameters:
sourceId (string, required): The Source ID.level (string): Filter by log level.channel (string): Filter by channel.search (string): Search text.searchRegex (bool): Enable regex search.searchCaseSensitive (bool): Enable case-sensitive search.dateFrom (string): Filter by start date.dateTo (string): Filter by end date.timelineFormat (string): Format for timeline (minute, hour, day).GET /dashboard-stats
Summary statistics across all sources for the dashboard.
Query parameters:
timelineFormat (string): Format for timeline (minute, hour, day).DELETE /delete
Deletes a specific log file.
Query parameters:
sourceId (string, required): The Source ID.GET /download
Downloads a specific log file.
Query parameters:
sourceId (string, required): The Source ID.The bundle provides several events that allow you to customize its behavior without modifying the core logic. You can use these events to filter logs, change file permissions, or modify log entries.
Dispatched when collecting data for both folder structure and flat file list.
Danilovl\LogViewerBundle\Event\LogViewerDataEventstructure (list<LogViewerFolderStructure>): The hierarchical structure of log sources.canDelete, canDownload) globally for the entire application. Modifying the structure property directly affects what is shown on the dashboard and sidebar.Dispatched after fetching log entries for a specific file, but before they are sent to the frontend.
Danilovl\LogViewerBundle\Event\LogViewerEntriesEventDispatched before downloading or deleting a log file.
Danilovl\LogViewerBundle\Event\LogViewerDownloadEventDanilovl\LogViewerBundle\Event\LogViewerDeleteEventExample of an Event Subscriber:
use Danilovl\LogViewerBundle\Event\LogViewerDownloadEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LogViewerSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
LogViewerDownloadEvent::class => 'onDownload'
];
}
public function onDownload(LogViewerDownloadEvent $event): void
{
$source = $event->source;
// Logic to check if the user can download this file
if ($source->name === 'security.log') {
$event->stopPropagation();
}
}
}
You can create your own log parser if your logs are not in the standard Monolog format.
Implement the Danilovl\LogViewerBundle\Interfaces\LogInterfaceParser:
namespace App\Parser;
use Danilovl\LogViewerBundle\DTO\LogEntry;
use Danilovl\LogViewerBundle\Interfaces\LogInterfaceParser;
use Danilovl\LogViewerBundle\Interfaces\LogParserGoPatternInterface;
use Danilovl\LogViewerBundle\Util\DateNormalizer;
class MyCustomParser implements LogInterfaceParser, LogParserGoPatternInterface
{
private const string PATTERN = '/(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (?P<level>\w+) (?P<message>.*)/';
public function parse(string $line, string $filename): LogEntry
{
$match = preg_match(self::PATTERN, $line, $matches);
if (!$match) {
return new LogEntry(
timestamp: '',
level: 'INFO',
channel: 'custom',
message: $line,
file: $filename,
normalizedTimestamp: '',
context: []
);
}
return new LogEntry(
timestamp: $matches['timestamp'],
level: $matches['level'] ?? 'INFO',
channel: 'custom',
message: $matches['message'] ?? $line,
file: $filename,
normalizedTimestamp: DateNormalizer::normalize($matches['timestamp']),
context: []
);
}
public function getName(): string
{
return 'my_custom_format';
}
public function supports(?string $parserType): bool
{
return $parserType === 'my_custom_format';
}
public function getPattern(): string
{
return self::PATTERN;
}
public function getDateFormat(): string
{
return 'Y-m-d H:i:s';
}
public function getGoParserName(?string $parserType): string
{
return 'custom';
}
public function getGoPattern(?string $parserType): string
{
return '(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (?P<level>\w+) (?P<message>.*)';
}
}
If you use standard Symfony autowiring and autoconfiguration, your parser will be automatically registered and tagged with danilovl.log_viewer.parser.
Update your danilovl_log_viewer.yaml:
danilovl_log_viewer:
parser:
overrides:
'/path/to/your/custom.log': 'my_custom_format'
Or set it as the default:
danilovl_log_viewer:
parser:
default: 'my_custom_format'
The LogViewerBundle is open-sourced software licensed under the MIT license.
How can I help you explore Laravel packages today?