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

Log Laravel Package

illuminate/log

Laravel's logging component provides a PSR-3 compatible logger with powerful channel-based configuration, built-in Monolog integration, and handlers for single files, daily rotation, stacks, syslog, and error logs. It supports contextual logging and custom drivers for flexible application logging.

View on GitHub
Deep Wiki
Context7

Getting Started

In a Laravel application this package is already installed and wired — you almost never composer require it. Start in three places:

  1. config/logging.php — the default key and the channels array. This is the actual API surface you operate day to day.
  2. .env — LOG_CHANNEL (or LOG_STACK on Laravel 11+) and LOG_LEVEL.
  3. The source of truth for valid keys per driver: LogManager::createSingleDriver(), createDailyDriver(), createStackDriver(), createSlackDriver(), createSyslogDriver(), createErrorlogDriver(). If a config key isn't read there, it does nothing — the docs are thinner than the code.

First real use case — log an event with structured context, not string interpolation:

use Illuminate\Support\Facades\Log;

Log::info('Order placed', ['order_id' => $order->id, 'total' => $order->total]);

Then watch it: tail -f storage/logs/laravel.log, or php artisan pail for a live, filtered stream.

Standalone (non-Laravel) use is possible but awkward: the package needs illuminate/container, illuminate/config and illuminate/events to build a LogManager, or you can bypass it entirely and use the thin Logger wrapper over Monolog.

composer require illuminate/log monolog/monolog
use Illuminate\Log\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Logger as Monolog;

$logger = new Logger(new Monolog('app', [new StreamHandler('php://stderr')]));
$logger->info('Application started', ['pid' => getmypid()]);

Or bootstrap the manager when you want channel() semantics without the framework:

$app = new Illuminate\Container\Container;
$app->instance('config', new Illuminate\Config\Repository(['logging' => $config]));
$app->instance('events', new Illuminate\Events\Dispatcher($app));

$logger = (new Illuminate\Log\LogManager($app))->channel('daily');

The $config array is the same shape as config/logging.php. First use case in a framework app is always the same: one default channel, one withContext() call for a request ID, done.

Implementation Patterns

Channel per concern, not one giant file. Give payments, auth, and audits their own drivers so retention, formatter, and destination can differ:

'channels' => [
    'payments' => [
        'driver' => 'daily',
        'path' => storage_path('logs/payments.log'),
        'level' => 'info',
        'days' => 90,
        'permission' => 0664,
        'locking' => true,
    ],
    'stderr' => [
        'driver' => 'monolog',
        'handler' => StreamHandler::class,
        'with' => ['stream' => 'php://stderr'],
        'level' => env('LOG_LEVEL', 'debug'),
    ],
],

The stderr channel above is the correct default for Docker/Kubernetes — the container runtime collects it, you get no log-rotation bugs and no writes to a read-only filesystem.

Stacks for fan-out. 'driver' => 'stack', 'channels' => ['single', 'slack'] sends everything everywhere. A stack cannot filter per member, so if you only want errors in Slack, wrap the Slack handler instead:

$handler = new FilterHandler(new SlackWebhookHandler(/* ... */), ['error', 'critical', 'alert', 'emergency']);

Request context in middleware, instead of repeating array keys in every call:

Log::withContext(['request_id' => (string) Str::uuid(), 'user_id' => auth()->id()]);

withContext() returns a new logger instance — it does not mutate the default channel. Bind it per request or resolve it from the container.

Queue/Octane context propagation (Laravel 11+) uses the Context repository, which is far better than stuffing IDs into every call:

use Illuminate\Support\Facades\Context;

Context::add('user_id', $user->id);            // flat key, appears in every record's context
Context::push('steps', 'invoiced');            // accumulates into an array
Context::add('token', $token, hidden: true);  // available to logs, excluded from serialization

Register dehydrate/hydrate callbacks on the Context repository for values that must survive into a queued job.

Custom drivers go in a service provider's boot() (not a deferred one — the driver must exist before the channel is first resolved):

Log::extend('audit', function ($app, array $config) {
    $handler = new StreamHandler($config['path']);
    return new Logger(new Monolog('audit', [$handler]), $app['events']);
});

Scrub secrets and add metadata with a Monolog processor, or with a 'tap' => [App\Logging\RedactSecrets::class] class whose __invoke($logger) receives the built driver. Keep that parameter untyped and instanceof-check it — the argument differs between drivers.

Log exceptions with the exception key so Monolog's normalizer prints the stack trace:

Log::error('Payment capture failed', ['exception' => $e, 'order_id' => $id]);

In a real Laravel app prefer report($e), which routes through the exception handler and respects dontReport.

Testing. Three options, cheapest first:

Log::spy();
// ... act ...
Log::shouldHaveReceived('info')->withArgs(fn ($m, $c) => $c['order_id'] === 42);

Event::fake([MessageLogged::class]);   // assert on the MessageLogged event

For output-level assertions, push a Monolog\Handler\TestHandler via Log::extend() in the test and inspect getRecords().

Gotchas and Tips

  • A bad channel name fails silently. LogManager::get() catches InvalidArgumentException (typo'd channel, Driver [x] is not supported, closed stream) and returns an emergency logger pointed at storage/logs/laravel.log at debug. Your app looks fine and your logs vanish into the wrong file. Symptom to remember: "it logs, just not where I configured." Inspect what you actually got with Log::channel('payments')->getLogger()->getHandlers() — Logger::__call forwards to Monolog, so this works.
  • level is a severity floor, not a filter. 'level' => 'warning' silently discards info and debug. Setting 'level' => 'emergency' means the channel records essentially nothing. For "only this level, nothing above or below," wrap in FilterHandler.
  • env() inside config/logging.php returns null after php artisan config:cache. Standard Laravel footgun, but it bites hardest here because a null path or channel throws inside the manager and gets swallowed by the emergency-logger fallback above.
  • Mixing Monolog 2 handlers into a Monolog 3 install fails with signature errors on handle(): bool and formatter constructors. Check the PHP/Monolog floor in the composer.json of the branch you actually install and pin consistently.
  • Context leaks across long-lived processes. In Octane or a queue worker the repository persists between requests/jobs. withoutContext() (returns a clone) helps for a single call, but you generally need explicit Context::flush() in a terminating callback.
  • MessageLogged fires on every write. A listener that does I/O, or worse logs something itself, will tank throughput or recurse. Keep listeners cheap and guard against re-entry.
  • Container resolution order matters. Log::extend() and shareContext() must run before a channel is first resolved; once cached in LogManager::$channels the driver is reused for the process lifetime. Register in boot(), never lazily.
  • Daily files need days and, on shared hosts, locking. Also note the filename is laravel-YYYY-MM-DD.log; permission is ignored if the file already exists with other ownership.
  • Inject Psr\Log\LoggerInterface rather than using the facade in services and jobs — Laravel aliases log to it, so you get testability without Log::spy() gymnastics.
  • Extension points, in order of preference: Log::extend() for new drivers → Monolog processors for record mutation → tap classes for formatter/handler tweaks → listeners on MessageLogged for observation. Reach for the last two only when the first isn't enough, because they're the ones that behave differently per driver.
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.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules