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

Monolog Fluentd Bundle Laravel Package

brigthside/monolog-fluentd-bundle

A Symfony bundle that connects Monolog to Fluentd, forwarding structured application logs to a Fluentd collector for centralized aggregation, search, and analysis. Configure handlers and formatters through bundle configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the composer.json first, not the README. It tells you the two things that decide everything: the license (so you know whether you can vendor it) and the fluent/fluent-logger-php constraint (so you know whether the underlying transport is even installable on your PHP version). A Symfony 2 bundle almost certainly pins monolog/monolog: ~1.0 and a 2015-era Fluent logger — that combination will not resolve against Laravel 10/11 on PHP 8.2.

Then read exactly one file: the handler class under src/. Everything else in the repo — DependencyInjection/, Extension.php, Configuration.php, Resources/config/services.yml — is Symfony 2 service-container plumbing that has no meaning in Laravel and no equivalent you need. The handler is likely 40–80 lines wrapping Fluent\Logger\FluentLogger::post(). Read it, understand it, then throw it away and write your own. That is the whole "getting started" story: you are sourcing a pattern, not a dependency.

Do not composer require it. There is no Laravel bridge, no auto-discovery, no service provider. If you install it you get dead classes and a transitive dependency on an ancient Fluent logger.

Your first working use case, end to end, is roughly an hour:

  1. Stand up a collector locally so you can see records before you wire anything:
    docker run --rm -p 24224:24224 fluent/fluentd:latest \
      fluentd -c /fluentd/etc/fluent.conf
    # fluent.conf: <source>@type forward</source>  <match **>@type stdout</match>
    
  2. Install the transport only — composer require fluent/fluent-logger-php:^2.0 (check the current major against your PHP version; v1 supports PHP 5/7, v2 targets 7.2+).
  3. Port the handler into app/Logging/FluentdHandler.php against Monolog 3's LogRecord API:
    namespace App\Logging;
    
    use Fluent\Logger\FluentLogger;
    use Monolog\Handler\AbstractProcessingHandler;
    use Monolog\LogRecord;
    
    final class FluentdHandler extends AbstractProcessingHandler
    {
        public function __construct(
            private readonly FluentLogger $logger,
            private readonly string $tag,
            int|string|Level $level = Level::Info,
            bool $bubble = true,
        ) {
            parent::__construct($level, $bubble);
        }
    
        protected function write(LogRecord $record): void
        {
            $ok = $this->logger->post($this->tag, [
                'message'   => $record->message,
                'level'     => $record->level->getName(),
                'channel'   => $record->channel,
                'context'   => $record->context,
                'extra'     => $record->extra,
                'service'   => config('app.name'),
                'env'       => app()->environment(),
                'timestamp' => $record->datetime->format(DATE_ATOM),
            ]);
    
            if ($ok === false) {
                // do not throw — see Gotchas
                report(new \RuntimeException('Fluentd post failed'));
            }
        }
    }
    
  4. Register it as a custom channel in config/logging.php — custom + via is the idiomatic Laravel route and avoids abusing the handler/with constructor-arg convention:
    'fluentd' => [
        'driver' => 'custom',
        'via'    => fn () => new Monolog\Logger('fluentd', [
            new App\Logging\FluentdHandler(
                new Fluent\Logger\FluentLogger(
                    env('FLUENTD_HOST', '127.0.0.1'),
                    (int) env('FLUENTD_PORT', 24224),
                ),
                env('FLUENTD_TAG', 'app.'.env('APP_NAME', 'laravel')),
                env('LOG_FLUENTD_LEVEL', 'info'),
            ),
        ]),
    ],
    

First smoke test: LOG_CHANNEL=fluentd php artisan tinker → Log::info('hello', ['user_id' => 1]) and watch docker logs print the record. If that works, the rest is configuration.

Implementation Patterns

Stack it as a sink, never as the primary channel. The single most important pattern: the Fluentd channel is additive, sitting alongside the durable local record. Rollback is then a one-line config change, and a collector outage never costs you the log line entirely.

'stack' => [
    'driver'          => 'stack',
    'channels'        => ['daily', 'fluentd'],
    'ignore_exceptions' => true,   // critical
],

Derive the tag from the channel, not from a global config. Fluentd routing is by tag, so per-channel tags give you free routing in the collector without touching PHP. Either register several handlers with different tags, or compute the tag in write():

$tag = sprintf('%s.%s', $this->tagPrefix, $record->channel);
// app.myapp.payments, app.myapp.auth, app.myapp.jobs

Keep tags ASCII, dot-separated, and stable — the collector's <match> rules are effectively a public contract once other teams build dashboards on them.

Fix the record schema before volume arrives. Emitting $record->context raw is convenient for a day and unqueryable for a year. Normalize in the handler: flatten context to scalar keys, promote the ones you actually filter on (request_id, user_id, trace_id, job_class, duration_ms), and JSON-encode the rest into a single context field. Retrofitting structure onto a high-cardinality stream is expensive; a LogContext processor that runs before the handler is worth the extra file.

Decide the transport explicitly — it's usually the real answer. Direct app→Fluentd is not automatically correct. For Laravel in containers, the better pattern in most cases is JSON to stdout and Fluent Bit tail on the node, which removes the request-path network dependency entirely:

'stdout_json' => [
    'driver'    => 'monolog',
    'handler'   => Monolog\Handler\StreamHandler::class,
    'with'      => ['stream' => 'php://stdout'],
    'formatter' => Monolog\Formatter\JsonsFormatter::class, // or JsonFormatter
    'processors' => [App\Logging\AddServiceMetadata::class],
],

Reserve the direct-handler approach for cases where the app genuinely owns the routing decision — a sidecar on localhost:24224 is the pragmatic middle ground: still a socket, but local, low-latency, and buffered by Fluentd.

Make the sink swappable so tests don't need a network. Extract a one-method interface and inject it:

interface LogSink { public function post(string $tag, array $payload): bool; }

Bind FluentdSink in AppServiceProvider for production, ArraySink in tests. Your feature tests then assert Log::info('x') produced a tagged payload without a Fluentd container in CI.

Tune levels per channel. Fluentd is a network hop; routing debug at production traffic volumes is the fastest way to discover your collector's throughput ceiling. Set LOG_FLUENTD_LEVEL=info (or warning for the highest-traffic services) and keep daily on debug.

Reuse the connection. In Octane/Swoole or long-running queue workers, resolve the FluentLogger once from the container — not per record. Persistent workers amortize the TCP handshake; classic FPM will reconnect per request regardless, which is exactly why high-traffic PHP-FPM apps should prefer the stdout pattern.

Gotchas and Tips

Monolog 2 vs 3 is a hard failure, not a warning. HandlerInterface gained return types (handle(): bool, handleBatch(): void, close(): void) and write() now receives a LogRecord object instead of an array. A Symfony 2-era handler signature will produce a fatal Declaration must be compatible error at class-load time. Port the signature; don't try to shim it.

PHP 8 will reject the bundle's syntax outright. Expect create_function-adjacent patterns, untyped/loosely-typed signatures, dynamic properties (deprecated in 8.2), and possibly each(). If you vendor the file verbatim, expect to rewrite most of it — which is the argument for writing your own 50-line version with clean provenance instead.

FluentLogger::post() returns false on failure — it does not throw. Treat the return value as the only signal you get. The two failure modes are both bad: swallowing it means silent observability loss during exactly the incident you need logs for; throwing means a collector outage becomes 5xx on healthy requests. The mitigation is a fallback channel plus a counter: on false, write to the local channel and increment a metric (Log::channel('daily')->warning(...) plus a Prometheus/StatsD increment in the handler). Alert on the counter — an unmonitored swallow is how a logging outage stays invisible for a week.

ignore_exceptions on the stack is not optional. Without it, Laravel's stack channel re-throws handler exceptions during the log write, which in a report() path or an exception handler produces recursion or a masked original error.

Never put the Fluentd channel on the logger the framework uses to report its own failures, and avoid it inside app/Exceptions/Handler. A network call in the error path during a partial outage hangs the request instead of failing it fast. Keep LOG_CHANNEL pointed at a file/stderr channel and opt services in via Log::channel('fluentd') or a stack they explicitly choose.

Set a connect timeout, and know what the transport actually does. Verify in the fluent-logger-php source whether it's synchronous per-record connect, buffered, or fire-and-forget, and what the default timeout is. A blocking forward send on the request path adds tail latency at the p99 exactly when the collector is struggling; a bounded buffer trades latency for potential record loss on process exit. Neither default is safe without knowing which you have — write it down in the service's design note.

Sanitize before it leaves the boundary. Log records are a data-egress path. $record->context routinely contains Eloquent models, Authorization headers, emails, and raw request bodies. Serializing objects also produces enormous, unstable payloads (and can recurse). Normalize in a Monolog processor upstream of the handler and keep an explicit allowlist of fields; treat the Fluentd payload as publicly readable.

Exceptions in context serialize badly. ['exception' => $e] becomes either a giant object dump or a broken payload depending on the formatter. Add a processor that converts throwables to ['class', 'message', 'code', 'file:line'] before the record reaches the sink.

A stale persistent socket in Octane fails silently after a collector restart. Long-running workers hold one FluentLogger connection; if Fluentd restarts, post() starts returning false and never reconnects unless the client has retry logic. Either verify reconnection behaviour or recycle on the failure counter — and make sure your dropped-record metric is the thing that catches it.

Zero stars, zero dependents, a misspelled namespace (brigthside). Concretely: no CVE feed, no compatibility PRs, no one to escalate to. If you take any code from it, vendor it with attribution, pin it (or commit it), and assign a named owner in the platform team. Budget an internal design note and one test that exercises the Fluentd-down path — otherwise the first person to read this code will be someone debugging an incident.

Verify against a real collector before trusting it. Unit tests with a fake sink prove nothing about tag routing, buffer flush, or backpressure. Run one service against a local Fluentd with @type stdout, confirm the tag lands in the right <match>, then break it deliberately (stop the container) and confirm your fallback + counter fire instead of a 500.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager