1. Confirm the package identity before installing. The Composer name (php-monsters/laravel-xlog) and the repository name (laravel-extended-logger) differ, which is a common source of "package not found" friction. Verify with:
composer show php-monsters/laravel-xlog --all
If that returns nothing, inspect the repo's composer.json on the master/main branch for the real name field and require it from VCS instead:
"repositories": [{ "type": "vcs", "url": "https://github.com/php-monsters/laravel-extended-logger" }]
2. Install and read three files first. Before writing any config, open:
composer.json — check the illuminate/* and monolog/monolog constraints. This tells you immediately whether it targets Laravel 5–9 (Monolog 2) or Laravel 10+ (Monolog 3).src/*ServiceProvider.php — look for mergeConfigFrom, publishes, and any Log::extend / LogManager binding. That reveals whether you configure it as a driver or as a tap/formatter.config/xlog.php) — the channel names, level thresholds, and file paths it ships with.3. Publish the config.
php artisan vendor:publish --provider="PhpMonsters\XLog\XLogServiceProvider"
Use --tag= if the provider registers a tag; check the provider source for the exact tag string rather than guessing.
4. First use case: a dedicated, level-separated or context-rich log channel. The realistic day-one win for a package like this is not replacing daily, it's adding a channel you route specific events to — e.g. audit events, payment failures, or third-party API errors — so they don't get buried in the general application log.
Wire it into config/logging.php:
'channels' => [
'xlog' => [
'driver' => 'xlog', // confirm the exact driver key in the service provider
'path' => storage_path('logs/xlog.log'),
'level' => env('XLOG_LEVEL', 'debug'),
],
],
Then smoke-test it immediately in Tinker before touching application code:
php artisan tinker
>>> Log::channel('xlog')->info('xlog smoke test', ['user_id' => 1]);
Confirm the file appears in storage/logs/ and the line format is what you expect. If nothing is written, the driver name is wrong or the provider isn't registered — that's your first debugging fork.
5. Only then decide whether to make it the default. Leaving LOG_CHANNEL=stack and calling Log::channel('xlog') explicitly is the low-risk path. Changing LOG_CHANNEL globally couples your entire app to a package with a 2022 last release.
Stack composition rather than channel replacement. The safest production pattern is to add the channel to your existing stack so you keep Laravel's rotation and retention behaviour:
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'xlog'],
],
This is the pattern to prefer when the package adds enrichment (extra context, different formatting) rather than a completely separate sink.
Scoped channels for domain events. Use named channels per concern so retention and level thresholds differ:
Log::channel('xlog')->warning('Stripe webhook signature mismatch', [
'event_id' => $event->id,
'ip' => $request->ip(),
]);
This keeps a noisy integration channel at warning while the app log stays at debug.
Context enrichment via middleware + withContext. If the package's value is contextual (request ID, user, tenant), push the context once per request rather than passing it to every call:
// app/Http/Middleware/LogContext.php
public function handle($request, Closure $next)
{
Log::withContext([
'request_id' => (string) Str::uuid(),
'user_id' => optional($request->user())->id,
]);
return $next($request);
}
withContext is a core Laravel/Monolog feature, so this works whether or not the package implements its own context handling — it's the portable half of the solution.
Route the exception handler through it. For a real day-to-day payoff, send unhandled exceptions to the enriched channel from bootstrap/app.php (Laravel 11) or app/Exceptions/Handler.php::report():
public function report(Throwable $e): void
{
Log::channel('xlog')->error($e->getMessage(), [
'exception' => $e,
'url' => request()?->fullUrl(),
]);
parent::report($e);
}
Beware double-logging here — if the channel is already in the stack, you'll get two entries. Pick one mechanism.
Per-environment configuration. Keep levels and paths in .env so staging can be verbose without a code change:
XLOG_LEVEL=debug
XLOG_PATH=/var/log/app/xlog.log
Then php artisan config:cache in deploy. If you use config:cache, confirm the package merges defaults via mergeConfigFrom — packages that read config in a service provider constructor break under config caching.
Testing. Assert against the channel, not the filesystem:
Log::spy();
$this->post('/webhook', $payload);
Log::shouldHaveReceived('channel')->with('xlog');
Or, more robustly, bind a Monolog\Handler\TestHandler into the channel during tests so you can assert on records rather than mocking the facade.
Queued jobs. Context set by HTTP middleware does not survive into queue workers. If your logs rely on request_id or user_id, re-establish it in a job middleware or handle() method, otherwise your xlog entries from jobs will be missing the fields you depend on for correlation.
The Monolog 2 → 3 boundary is the most likely breakage. Laravel 10 moved to Monolog 3, and this package's last release (August 2022) predates that. Custom Monolog handlers built against Monolog 2 will throw signature/type errors on Laravel 10+. Before adopting, install on your actual Laravel version in a throwaway branch and run a write test — do not trust the README's version claim.
Read the source; it's small. With ~10 stars and no dependents, there's essentially no community knowledge base, no Stack Overflow answers, and no third-party tutorials. The entire documentation surface is the README plus a handful of source files. Budget 20 minutes to read every .php file in src/ — it's almost certainly faster than debugging it blind.
Treat it as abandoned-ish and plan accordingly. Last release 2022, zero dependents. Two mitigation strategies, both cheap:
app/Logging/ and register with Log::extend(). Most "extended logger" packages are 30–80 lines around a Monolog handler; reimplementing is often less work than maintaining a dead dependency.composer.json ("php-monsters/laravel-xlog": "1.0.0" not "^1.0") so a future incompatible tag can't slide in.Config cache is the #1 "it works locally" failure. After any edit to config/logging.php or the published xlog.php, run php artisan config:clear. A stale bootstrap/cache/config.php will silently ignore your new channel.
File permissions bite in containers. Custom paths outside storage/logs aren't created or chowned by Laravel. If the package writes to /var/log/app/, ensure the www-data/php-fpm user owns the directory, and note that Monolog will throw during an exception if it can't open the stream — a logging failure that masks the original error.
Watch the level threshold, not just the driver. Setting level => 'debug' on a channel that receives chatty integration events will fill disks. Pair the channel with days if the underlying handler supports rotation, and check whether the package's handler rotates at all — some custom handlers write to a single ever-growing file with no retention.
Double-logging from stack + explicit calls. The most common self-inflicted bug: adding the channel to stack and calling Log::channel('xlog') in the same code path. Audit for this when log volume suddenly doubles.
Extension points to look for. The three that matter in a small package like this: a custom Monolog Formatter (controls the line format — override it rather than post-processing logs), a custom Handler (controls the destination), and the driver name registered via Log::extend(). Any of these can be swapped independently without forking the whole package, which softens the maintenance risk.
Debugging checklist when nothing is written. In order: (1) is the provider auto-discovered — php artisan about lists providers; (2) is the driver name correct — a typo throws InvalidArgumentException: Driver [xlog] is not supported, but a swallowed exception won't; (3) is the level threshold filtering you out; (4) can the process write to the path; (5) is config:cache stale. Check storage/logs/laravel.log first — Laravel often reports why the custom channel failed there.
How can I help you explore Laravel packages today?