presta/datadog-bundle
Symfony bundle for Datadog integration: send StatsD metrics and APM traces from your app with minimal configuration, plus helpers for timing code and tracking custom events. A lightweight way to add Datadog monitoring to existing Symfony projects.
Reality check before anything else: this is still a Symfony bundle, not a Laravel package. There's no service provider, no config/datadog.php, no extra.laravel block. Composer will install it, but nothing in Laravel will pick it up. Treat the bundle as a reference implementation of a Monolog → Datadog log intake handler rather than a dependency you "use."
What changed in v2.0.0 that you need to know up front: the minimum platform moved to Symfony ^6.4|^7.0 and PHP 8.1+ (8.2+ for Symfony 7), and — critically for Laravel porters — the DatadogHandler constructor no longer takes Symfony\Component\Security\Core\Security. It takes a TokenStorageInterface (security.token_storage) instead, because Security was deprecated in Symfony 6.2 and removed in 7.0. On Symfony with default config this is a no-op. On Laravel it means the handler now has a hard type-hint on a Symfony Security interface that your app does not have wired up.
Where to look first:
README.md — still small; note the YAML config keys shown.DependencyInjection/Configuration.php — the authoritative config tree (key names, defaults, required fields). The README historically lags behind this.DependencyInjection/PrestaDatadogExtension.php — now extends Extension rather than ConfigurableExtension (internal, the class is final), but still the place to see how config values are mapped into the handler constructor.Resources/config/services.* — the handler service definition. Check how security.token_storage is injected; you'll need to replicate or stub that argument.Presta\DatadogBundle\Monolog\Handler\DatadogHandler) — confirm the full constructor signature, transport (curl vs. HTTP client vs. UDP), and formatter before copying anything.Minimal steps on Symfony: composer require presta/datadog-bundle:^2.0, register the bundle in config/bundles.php, set presta_datadog.api_key, env, service, then attach the handler to a Monolog channel. First use case: ship application logs to Datadog Log Management while keeping a local file handler for development. If you were on 1.x with Symfony 6.4, upgrading the package alone is sufficient unless you instantiate the handler or redefine its service yourself.
Minimal steps on Laravel: vendor the handler class into app/Logging/ (or require the package and reference its FQCN), then add a channel:
// config/logging.php
'datadog' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'info'),
'handler' => \Presta\DatadogBundle\Monolog\Handler\DatadogHandler::class,
'handler_with' => [
'apiKey' => config('services.datadog.key'),
// v2: a TokenStorageInterface is now required — see Gotchas
'tokenStorage' => new \Symfony\Component\Security\Core\Authentication\Token\Storage\NullTokenStorage(),
],
],
Then set LOG_CHANNEL=datadog in production. Verify the constructor args against the class — this is the single most common porting failure, and v2 has changed the arg list, so any snippet you find from a 1.x-era blog post is now wrong.
datadog as its own channel and select it via env (LOG_CHANNEL, or a stack in logging.php). Keeps local/daily behavior untouched and makes rollback a one-line env change.Security helper. On Laravel, either pass NullTokenStorage and add user_id yourself via tap, or (better, if you're vendoring anyway) strip the token-storage argument out of your copy entirely — you already have auth()->id(). Don't pull symfony/security-core into a Laravel app just to satisfy a null check.tap for request context. Add request_id, user_id, route name, tenant, and release version once at the channel level rather than sprinkling them into every Log::info() call. Datadog facets need consistent top-level attributes.Log::info('order.paid', ['order_id' => $id, 'amount_cents' => $cents])) so fields become facets and can be filtered/aggregated. Preformatted strings defeat the purpose.BufferHandler (or FingersCrossedHandler) so a burst of errors doesn't spawn one request per record. Logging must never be your slowest code path.datadog-agent, sending to localhost (UDP/HTTP) is cheaper, lower-latency, and survives intake outages better than the HTTP intake handler. The bundle's handler is fine as a fallback for environments without an agent.staging/production. Locally, a failing remote log should be a no-op, not a stack trace in your console.DatadogHandler manually — which is exactly what the Laravel port above does — you must swap the Security argument for a TokenStorageInterface. handler_with does no transformation, so an old 'security' => ... key will now throw a TypeError or an unresolvable-argument exception from Monolog's handler factory. This is the single most likely upgrade break in a Laravel context.^6.4|^7.0; Symfony 5.x and 6.0–6.3 are dropped. If you're maintaining an older Laravel app on PHP 8.0, this release is not installable — stay on 1.x. Note also that CI covers 8.1 / 8.2 / 8.5 against 6.4 and 7.4, so 8.3 and 8.4 aren't explicitly in the matrix; they'll almost certainly work, but they haven't been tested by the maintainer.monolog/monolog (3.x) and symfony/http-foundation are now declared dependencies rather than assumed. Monolog 3 is what Laravel 10/11 already ship, so that aligns fine — but if you're on an older Laravel pinned to Monolog 2, this package will fight your composer resolution.max with an empty baseline and a real CI matrix is better hygiene than most abandoned bundles get — but it's still a single-handler package. The handler plus DI glue is small enough to vendor and own; that remains the correct move for most Laravel teams.handler_with. Confirm constructor parameter names exactly (apiKey vs api_key, tokenStorage vs token_storage, etc.) — Monolog does not transform keys.api_key, source, tags) are mapped to constructor arguments by the extension. When porting to Laravel, read Configuration.php and the extension to get the real names and defaults; the README is not reliable for this. Re-read them after v2 regardless of what you had at 1.x.config/services.php → env('DD_API_KEY'), never commit it, and always run php artisan config:cache after changes.ignore_exceptions on the channel or a WhatFailureGroupHandler wrapper). A Datadog outage should degrade observability, not your app.info, drop debug in prod, and sample noisy paths. The handler choice matters far less than what you send.bin/console debug:config presta_datadog and debug:container <handler.service.id> to see resolved arguments — with v2 this is the fastest way to confirm the token-storage injection is what you expect. On Laravel: dump config('logging.channels.datadog') after config:cache and temporarily add a single channel in parallel to confirm records are actually leaving the app.curl or an unbounded Guzzle client, verify TLS, proxy, and DNS in your container. Silent failures are common — always pair the Datadog channel with a local fallback handler (e.g., a StreamHandler to stderr) so logs aren't lost when intake is unreachable.JsonFormatter if you want a specific wire format. PrestaDatadogExtension is now final, so subclassing it is out — use decoration, not inheritance. Avoid forking the bundle unless you're already committed to owning it.How can I help you explore Laravel packages today?