Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Web Logs Laravel Package

mojtabaahn/laravel-web-logs

Browse and read your Laravel application logs directly in the browser. This lightweight package adds a simple web UI for viewing, searching, and filtering log files—no SSH or server access needed—with configurable routes, middleware, and access control.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Confirm it will even install. The package is archived (last release Feb 2022), so its composer.json almost certainly pins illuminate/* to Laravel 8/9. Check before anything else:

composer show mojtabaahn/laravel-web-logs --all | grep -i illuminate
# or read vendor/mojtabaahn/laravel-web-logs/composer.json

On Laravel 10+ you'll get a dependency conflict — either fork it and widen the constraints or stop here and use opcodesio/log-viewer instead (same feature set, maintained).

2. Install and publish.

composer require mojtabaahn/laravel-web-logs
php artisan vendor:publish --tag=web-logs-config   # check `php artisan vendor:publish` output for the exact tag

3. Read the published config before browsing. config/web-logs.php is the whole package: log directory path, file pattern (usually *.log), route prefix, and — most importantly — middleware. Key names differ slightly between versions, so trust the published file, not the README.

4. Lock it down immediately. Add auth (and a can: gate) to the middleware array before you ever expose the route. Log files contain stack traces, SQL, request payloads, tokens, and emails.

5. First use case. Locally: reproduce a 500, open http://localhost:8000/web-logs (or your prefix), pick laravel.log, filter level ERROR, click the timestamp to jump to context. That's the whole value proposition — no SSH, no tail -f, no leaving the browser.

Where to look first in the source (vendor/mojtabaahn/laravel-web-logs/):

  • src/Http/routes.php (or routes/) — the actual endpoints and param names.
  • The controller that reads files — this tells you how it parses lines, whether it loads the whole file, and whether the file param is sanitized.
  • config/web-logs.php — the only supported extension point.

Implementation Patterns

Gate-based access (do this, not just auth).

// AppServiceProvider::boot() (Laravel 11) or AuthServiceProvider (≤10)
Gate::define('viewWebLogs', fn (User $user) => $user->is_admin());
// config/web-logs.php
'middleware' => ['web', 'auth', 'can:viewWebLogs'],

Do not omit web — auth and can need the session middleware to resolve the user, and ordering matters.

Environment-aware config so production is never exposed accidentally.

'middleware' => array_filter([
    'web',
    app()->isLocal() ? null : 'auth',
    app()->isLocal() ? null : 'can:viewWebLogs',
]),

Better: also add a hard kill switch.

// a one-line middleware in front of everything
abort_unless(config('app.debug') || app()->isLocal(), 404);

Return 404, not 403 — no reason to advertise the endpoint.

Register only where you want it. Because the service provider is auto-discovered, you can't easily skip it per environment. The clean options: a config-driven enabled flag if the version supports it, a global middleware returning 404, or move the package to require-dev and only install locally.

Request-ID correlation is the highest-value habit. If every log line carries a request ID, the viewer becomes a real triage tool rather than a pretty cat:

// middleware
Log::withContext(['rid' => $rid = (string) Str::uuid()]);
$response->headers->set('X-Request-Id', $rid);

Then in the viewer, paste the rid from the user's error report and get the full trace for that request — including jobs and nested logs, if you propagate the context.

Separate channels, not one giant file. Use daily plus dedicated channels (payments, jobs, webhooks) and set LOG_DAILY_DAYS retention. The viewer's file list becomes your channel switcher, and a runaway channel doesn't drown everything else. Verify your channels use the default Monolog line formatter — see Gotchas.

Triage workflow that actually holds up:

  1. Error surfaces (Sentry/Slack/alert).
  2. Copy the request ID or timestamp.
  3. Open viewer → filter ERROR (or search the ID) → read the stack trace.
  4. Cross-reference with php artisan tinker / a queue retry.
  5. Fix, redeploy, verify the log stops growing errors.

Keep the viewer for local and staging; production belongs to Sentry/Telescope/CloudWatch. This package reads a local disk — it's useless on Vapor, Cloud Run, or any read-only/ephemeral filesystem.

Pairing it with the CLI. The viewer is a convenience, not a replacement: tail -f storage/logs/laravel.log | grep -i error and less +F are still faster for streaming. Reach for the web UI when you don't have shell access — Windows dev, shared hosting, a locked-down PaaS.

Admin panel integration. Point prefix at something inside your existing admin namespace (e.g. admin/logs) so it inherits your layout/nav and your existing auth stack, rather than standing alone at /web-logs with a separate middleware stack to keep in sync.

Gotchas and Tips

Maintenance risk is the headline. Archived since Feb 2022, 154 stars, no security response. Audit the controller before exposing it anywhere non-local, and budget a migration to opcodesio/log-viewer at some point — the feature set is a superset and it handles big files and JSON logs properly.

The default middleware is the trap. Fresh installs commonly ship with ['web'] only, i.e. a public route that dumps your entire log directory — including APP_KEY in a boot-time stack trace, DB credentials in a connection error, and full request bodies. Set the gate before the first php artisan serve.

config:cache and route:cache will make you think the config is broken. Editing config/web-logs.php on a cached production box does nothing until php artisan config:cache re-runs. Same for changing prefix or middleware — the cached route table wins. If the route 404s after a deploy, run php artisan route:list --path=web-logs first.

Custom Monolog formatters break everything. The parser assumes the stock format:

[2024-05-01 10:22:31] production.ERROR: Something failed {"exception":"..."}

Switch to JsonFormatter, a custom LineFormatter with a different pattern, or a non-default timestamp format and you'll get unparsed lines: no level filter, no timestamp grouping, everything lumped as one entry. Check config/logging.php before blaming the package.

Multi-line stack traces and "one entry" grouping. The viewer groups entries by the [timestamp] env.LEVEL line. Filters and pagination operate on those groups. A trace without its header line (e.g. from a mid-write rotation) shows up as an orphan blob with no level — expected, not a bug.

Whole-file reads are the scaling wall. Versions of this package load the log into memory to render it. A 500 MB laravel.log will either time out or OOM your PHP process — and the process doing the OOMing is your web container. Keep files small: daily channel, LOG_DAILY_DAYS=7, and manual truncation (: > storage/logs/laravel.log) on staging. If you must keep a big file, check the source for a tail/limit parameter before you open it in a browser during an incident.

Permission ownership in Docker is the classic silent failure. php artisan run as root in a container creates root:root laravel.log; PHP-FPM runs as www-data and can't read it — the viewer shows an empty list or an empty file, and nothing in laravel.log explains why. Either run artisan as the app user, or chmod -R g+r / fix the group. Same story for Kubernetes read-only mounts.

Path handling. The file selector takes a filename from the query string. Confirm the version you install uses basename() and restricts to the configured path — and be aware that a symlink inside storage/logs pointing outside it may be followed. Don't symlink /var/log into the logs directory and call it done.

Timezone drift. Entries are stamped in APP_TIMEZONE at write time. Change the app timezone and old logs now "lie" relative to new ones — correlation by timestamp goes wrong during the transition. Prefer request IDs; they don't drift.

Front-end assets. If the UI pulls CSS/JS from a CDN, an air-gapped or firewalled environment renders a bare HTML table. Not broken — just unstyled. Check the Blade layout if it looks wrong.

After a Laravel upgrade that "breaks the page": php artisan view:clear && php artisan route:clear before debugging the package itself. Cached compiled views from an older Blade version are a common red herring.

Log level confusion. APP_DEBUG=false plus LOG_LEVEL=error means info/debug lines never get written — the viewer isn't filtering them out, they don't exist. If "nothing shows up," verify the file on disk with wc -l and tail -1 before touching config.

Don't leave it reading a path that grows unbounded. Set a channel per concern, cap retention, and treat the viewer as read-only tooling for a bounded directory. The moment storage/logs becomes multi-gigabyte shared storage, this package costs you more incidents than it solves.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package