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 Gelf Logger Laravel Package

hedii/laravel-gelf-logger

A Laravel package that adds a GELF log driver so your application can send structured logs directly to Graylog. Configure the host, port, and transport, then use Laravel’s logging system to ship messages with levels and context.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package like any Laravel driver package:

composer require hedii/laravel-gelf-logger

Laravel package auto-discovery registers the service provider, which calls Log::extend('gelf', ...). No migrations, no published config, no facade to alias.

The first thing to look at is config/logging.php. Add a channel using the gelf driver:

'channels' => [
    'gelf' => [
        'driver'     => 'gelf',
        'host'       => env('GELF_HOST', '127.0.0.1'),
        'port'       => env('GELF_PORT', 12201),
        'transport'  => env('GELF_TRANSPORT', 'udp'), // udp | tcp | http | https
        'level'      => env('LOG_LEVEL', 'debug'),
        'chunk_size' => 1420,
        'facility'   => env('GELF_FACILITY', 'laravel'),
    ],
],

Then wire it into your environment:

LOG_CHANNEL=stack
LOG_STACK=single,gelf
GELF_HOST=graylog.internal
GELF_PORT=12201
GELF_TRANSPORT=udp

On the Graylog side, create a GELF UDP input on port 12201 (or GELF TCP / GELF HTTP to match). Once the input shows "Running", you can verify end-to-end from tinker:

php artisan config:clear
php artisan tinker
>>> Log::channel('gelf')->info('hello graylog', ['deploy' => 'v1.2.3']);

Search Graylog's "All messages" for hello graylog. When that message appears, you know the driver, transport, network path, and input are all correct — everything after that is tuning fields and routing.

The first real use case is almost always the stack channel: keep local file logging for your own tail -f workflow and add gelf alongside it so production/infra logs land in Graylog without changing a single Log::info() call in application code.

Implementation Patterns

Stack channel as the default. Don't replace single with gelf; append it.

'stack' => [
    'driver'   => 'stack',
    'channels' => ['single', 'gelf'],
    'ignore_exceptions' => false,
],

This keeps storage/logs/laravel.log intact for local debugging, php artisan tail, and CI where no Graylog is reachable. Set LOG_STACK=single in phpunit.xml and .env.testing so tests never attempt a network send.

Per-environment configuration. The cleanest pattern is to leave config/logging.php static and drive the channel via env. Guard against accidentally sending from dev machines:

'gelf' => [
    'driver'    => 'gelf',
    'host'      => env('GELF_HOST', '127.0.0.1'),
    'port'      => (int) env('GELF_PORT', 12201),
    'transport' => env('GELF_TRANSPORT', 'udp'),
    'level'     => env('GELF_LEVEL', 'info'),
    'tap'       => null,
],

If GELF_HOST is unset locally, the driver will try 127.0.0.1:12201 and silently no-op (UDP) or throw on connect (TCP). Prefer to simply not include gelf in LOG_STACK outside deployed environments.

Dedicated channels for domain events. Use Log::channel('gelf') explicitly when you want a log line to always reach Graylog regardless of the default stack — for example audit events or payment state transitions:

Log::channel('gelf')->info('order.paid', [
    'order_id' => $order->id,
    'amount'   => $order->total,
    'gateway'  => $order->gateway,
]);

Laravel/Monolog context arrays become GELF additional fields (the library prefixes them with _ on the wire, which is what Graylog expects). Keep context flat — scalars and shallow arrays. Deeply nested objects either get serialized into a blob or dropped, depending on version, and you lose the ability to facet on them in Graylog.

Attaching context globally. For per-request metadata (request ID, tenant, deploy SHA), use Laravel's context API rather than repeating fields at every call site:

Log::withContext([
    'request_id' => (string) Str::uuid(),
    'release'    => config('app.release'),
]);

Every subsequent Log::info() — including the gelf channel on the stack — will carry those fields. This is the single most useful integration point for making Graylog actually searchable.

Exceptions. report() and unhandled exceptions go through the default logger, so once gelf is on the stack, stack traces flow to Graylog automatically. The full_message field will contain the trace. For UDP this is where chunking matters (see below); for TCP/HTTP it just works. If traces are enormous, consider a separate channel bound to Log::channel('gelf') only for error and above, and keep the file channel at debug.

Queued jobs and Octane. Because logging happens inside the request/job lifecycle, Log::channel('gelf') works identically in queued jobs and Octane workers. Tag job logs with context so you can correlate:

Log::withContext(['job' => static::class, 'attempt' => $this->attempts()]);

A thin wrapper for structured events. If your team starts emitting dozens of business events, wrap it once:

final class EventLog
{
    public static function record(string $event, array $payload = []): void
    {
        Log::channel('gelf')->info($event, $payload + [
            'env' => app()->environment(),
        ]);
    }
}

This gives you one place to later swap transports, add sampling, or route to a different channel without touching call sites.

Testing. There's no built-in fake. Use Log::spy() and assert shouldHaveReceived('info') against the default channel, or point the gelf channel at single/errorlog in phpunit.xml. If you truly need to assert GELF payloads, stand up a UDP listener in the test and decode, but that's rarely worth it — assert on your application's log calls, not the driver.

Gotchas and Tips

UDP is fire-and-forget. The default transport gives no delivery guarantee, no ordering, and no error signal. If a log line is critical for audit or billing reconciliation, use tcp or http. Reserve UDP for high-volume debug noise where losing a few lines is acceptable.

Chunk size must fit the MTU. GELF over UDP chunks payloads larger than chunk_size. The default of 1420 is chosen to stay under a 1500-byte MTU after IP/UDP headers. If you're on a VPN, overlay network, or encapsulated traffic (VXLAN, WireGuard), lower it to 1200–1300 or you'll get silent drops on large stack traces. Graylog must also have "Allow chunked messages" enabled on the input — if exception traces never arrive but short messages do, this is almost always the cause.

GELF_HOST is the Graylog server, not the message source. There are two "host" concepts: the destination address in the config, and the GELF host/source field inside each message. In containers, the source field defaults to the container hostname, so Graylog's source list fills up with random Docker IDs. You'll want to map that to something meaningful — check the package README for the exact option name (facility and similar are supported), and consider setting it from APP_NAME plus environment.

HTTP transport needs the Graylog GELF HTTP input, not the web UI port. Set transport => 'http', point port at the GELF HTTP input (commonly 12201, sometimes 12202), and make sure the path matches what the input expects (/gelf by default). For https, the input must be behind a TLS terminator and the certificate must validate against the container's CA bundle. Self-signed internal certs are the usual failure, and because the driver swallows transport errors into Monolog, you often see nothing in laravel.log — check storage/logs for Failed to send lines.

Config cache will bite you. After editing config/logging.php or changing GELF_* env values, run php artisan config:clear (or config:cache in deploy). A stale cached config with a typo'd driver name causes Laravel to throw Driver [gelf] is not supported at boot, which is confusing when the file looks correct.

Level filtering has two layers. LOG_LEVEL controls the default stack, and the gelf channel's own level filters separately. If you set LOG_LEVEL=error but want info business events in Graylog, use the dedicated channel (Log::channel('gelf')) with its own level => 'info'.

Don't log secrets or full payloads. Once GELF is on the stack, Log::debug('incoming', $request->all()) ships passwords, tokens, and PII to a third-party system that retains messages by default. Add a tap processor or a withContext scrubber. This is the biggest operational risk of adopting centralized logging.

Synchronous network on the hot path. Each Log::info() performs the transport write inline. Over TCP with a slow/restarting Graylog, this can add latency to every request. Mitigations: use UDP for verbose logs, lower the channel level in production, or route structured events through a queued listener instead of logging directly.

Context serialization quirks. Monolog will happily stringify an Eloquent model into a giant JSON blob if you pass it in context. That blows past MTU/chunk limits and makes Graylog unusable. Always map models to scalars ($user->id, $user->email) before logging. If you need the full object, log it to the single channel only.

Debugging "nothing arrives". Work outward: (1) Log::channel('gelf')->info('x') from tinker; (2) nc -u -l 12201 locally to confirm packets leave the app host; (3) tcpdump/ss to confirm they reach the Graylog host; (4) check the Graylog input state and its "Show received messages" tab; (5) check storage/logs/laravel.log for swallowed transport errors. In Docker Compose, GELF_HOST must be the service name, and in Docker Desktop it may need host.docker.internal.

Extension points are minimal by design. The package is a thin Monolog handler wrapper around graylog2/gelf-php. If you need custom fields on every message, do it with Log::withContext() or a shared helper, not by subclassing the driver. If you need retry/buffering semantics, put a queue between your application and Graylog rather than expecting the package to provide it.

Version pinning. The package tracks Laravel's Log extension API, which has been stable since 5.5 but is not formally part of Laravel's BC promise. Pin the major version in composer.json and run composer update --dry-run before Laravel upgrades, since a broken Log::extend registration only surfaces at boot.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle