tomatophp/tomato-logs
Tomato Logs gives your Laravel and TomatoPHP admin panel a simple way to browse, filter, and manage application log files from one place, so debugging production issues no longer means digging through the server filesystem.
Confirm what the package actually does before you plan around it. The description claims an activity log viewer (create/update/delete events with user attribution), but the package name, the underlying jackiedo/laravel-log-reader dependency, and the release notes all point to a file log viewer — list storage/logs, open a file, filter by level/date/keyword, download, clear, refresh. Verify against vendor/tomatophp/tomato-logs/src/ in your pinned version before you promise a stakeholder an audit trail. If you need activity logging, that is spatie/laravel-activitylog, not this.
Prerequisites — do not skip this gate. Laravel 10/11, PHP 8.1+, Filament 3, and tomatophp/tomato-admin already installed and working. This is a Filament panel plugin; it registers a screen inside an existing panel. On a bare Laravel app it buys you a controller and a config file you still have to wire up. Off-stack, reach for opcodesio/log-viewer instead.
Install:
composer require tomatophp/tomato-logs
php artisan tomato-logs:install # confirm signature in src/Console before assuming
# fallback if no install command in your pinned version:
php artisan vendor:publish --tag=tomato-logs-config
php artisan vendor:publish --tag=tomato-logs-assets
Register the plugin in app/Providers/Filament/AdminPanelProvider.php — confirm the exact FQCN and plugin class name in src/, the plugin API changed between Filament majors:
use TomatoPHP\TomatoLogs\TomatoLogsPlugin; // verify
public function panel(Panel $panel): Panel
{
return $panel->plugins([
TomatoLogsPlugin::make(),
]);
}
Where to look first (ten minutes, end to end): the plugin class and its register()/boot(), the config file (log path, route prefix), the page/resource class that renders the viewer, and whether any authorization gate exists. That last one is the seam you will almost certainly need to add yourself.
First use case — the loop this exists to shorten: open the admin panel, confirm the Logs entry lists storage/logs/laravel.log, trigger a 500 in staging, refresh, filter by error, read the trace. No SSH, no tail -f, no waiting on infra access. If that loop does not improve for your team, the package is not earning its dependency.
Before you go further, set the channel to daily in config/logging.php. Every workflow below is faster when "yesterday" is one discrete file rather than a scroll through a 400 MB monolith.
Gate registration, not just the route. Assume the package ships no authorization until you have proven otherwise. Two layers, both cheap:
// 1. The menu entry never exists for non-admins
->plugins([
...(auth()->user()?->hasRole('admin') ? [TomatoLogsPlugin::make()] : []),
])
// 2. Route-level defense in depth — find the route name first
// php artisan route:list | grep -i log
Gate::define('viewLogs', fn (User $user) => $user->hasRole('admin'));
A low-privilege user must not see stack traces, SQL with bound parameters, or request payloads. Prove it with a test rather than a code review:
it('forbids non-admins from the log viewer', function () {
$this->actingAs(User::factory()->create())
->get('/admin/logs') // confirm actual path from route:list
->assertForbidden();
});
That test is your canary after every Filament/Laravel upgrade.
Point the config at what you actually write. Publish and edit the config to match config/logging.php:
return [
'path' => storage_path('logs'),
// 'allowed_extensions' => ['log'],
];
Day-to-day workflows worth standardizing:
error filter + search on a request ID, user email, or order number. The search box is the fastest path from "customer says checkout failed" to the guilty line.Keep the integration thin so replacement is a deletion, not a rewrite. Last release 2024-04-29, zero dependents, no issue triage. Plan for replacement:
use the package's classes, view namespaces, or config keys directly — route it through your own thin wrapper.opcodesio/log-viewer, the diff should be a plugin registration and a config file.Pre-commit the fork trigger now. Write it down: "if this breaks on the next Laravel or Filament major, we fork under our org rather than block the upgrade." Surface area is a controller, a config, and views — roughly a day. Deciding at the moment of a blocked upgrade is what turns a small dependency into an incident.
The description and the code disagree. Activity-log semantics (create/update/delete, user attribution, filters by subject and date) appear nowhere in a package that reads log files. Read the source before repeating the description in a design doc.
Large files time out. The reader loads file contents into memory. A single laravel.log grown to hundreds of MB gives you a slow or failed request. Fix the cause: switch to daily, cap with LOG_MAX_FILES, and never point this at a single-file channel in production.
A blank UI is almost always filesystem permissions. If www-data cannot read storage/logs, you get an empty list rather than an error. ls -l storage/logs, confirm the FPM user can read, and check disk space — a full disk presents the same symptom.
Rotation mid-read truncates output. Cosmetic, but it looks like a corrupted stack trace during an incident. Refresh and re-read before concluding anything.
Local disk only. No S3, no CloudWatch, no Loki, no shipped streams. If your logs go off-box, this viewer reads an empty local directory and tells you nothing.
Clear has no undo. Wrap or disable that action in production. At minimum, restrict it to environments where losing the log is acceptable.
Route prefix collisions. Check php artisan route:list immediately after install; if the panel path or your app already owns that segment, adjust the panel path or the package's prefix in config.
Config changes "not taking effect" → clear caches. php artisan config:clear and view:clear before you debug a config value that seems ignored.
Filament v2 vs v3 registration differs. A mismatch is a hard blocker, not a soft degradation. Run composer why-not against your locked versions before committing to a plan.
Pin the exact version. composer require tomatophp/tomato-logs:1.1.2 — no floating constraint on an unmaintained, single-maintainer package with no security disclosure channel.
It is a developer convenience, never monitoring. No alerting, no aggregation, no retention policy, no cross-host search, no audit trail. It shortens triage in dev and staging. If someone proposes it as your observability story, correct that misread immediately.
How can I help you explore Laravel packages today?