Usage

Once the bundle is installed and url + key are set, exception reporting works with no further code. Log forwarding and manual reporting take one extra step each.

Automatic exception capture

The bundle registers monitor.exception_subscriber, an event subscriber listening on Symfony's kernel.exception event at priority -64 (low, so framework listeners run first, the bundle only observes). For every unhandled throwable it:

  1. checks the configured environments against kernel.environment and skips when the current environment is not allowed;
  2. builds a payload from the throwable (class, message, file, line and the stack trace, full by default);
  3. attaches request context, the HTTP method and URL, plus the environment and release;
  4. scrubs sensitive keys and POSTs the result to /api/v1/ingest.

HTTP exceptions with a status code below 500 (NotFoundHttpException and other expected client errors, such as bot probes hitting 404s) are skipped: they are never reported and never count against your event quota. HTTP exceptions of 500 and above are reported like any other throwable.

It is additive: it never alters the response, never stops propagation, and never throws. If the server is unreachable, the send fails silently.

The subscriber reports throwables that reach the kernel. Exceptions you catch and handle yourself are, by definition, not unhandled, report those manually (below).

Forwarding logs

Set logs.enabled: true to turn log forwarding on. The Monolog handler service monitor.log_handler is always registered while the bundle is enabled; with logs.enabled: false it simply drops every record, so a monolog.yaml referencing it keeps compiling. The handler:

  • buffers records at or above logs.level;
  • honours the environments allowlist exactly like the exception subscriber: outside the allowed environments, no record leaves your application;
  • skips records that carry an exception in their context: those are already covered by the exception pipeline;
  • flushes to /api/v1/logs in batches of logs.max_batch, and again when the handler closes at the end of the request.

Registering the service is not enough on its own, you must attach it to Monolog as a service handler:

yaml
# config/packages/monitor.yaml
monitor:
    logs:
        enabled: true
        level: warning
        max_batch: 200
yaml
# config/packages/prod/monolog.yaml
monolog:
    handlers:
        monitor:
            type: service
            id: monitor.log_handler

Now records flowing through Monolog (level warning and above by default) are batched and shipped to your dashboard. This requires monolog/monolog ^3.0 in your application.

Reporting manually

The shared Reporter is registered as the service id monitor.reporter. Because it is a private service that is not aliased to its class name, plain autowiring by type (Reporter $reporter) will not resolve it, wire it explicitly.

Using the #[Autowire] attribute on a constructor argument:

php
use QuietGuard\Monitor\Reporter;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class CheckoutService
{
    public function __construct(
        #[Autowire(service: 'monitor.reporter')]
        private readonly Reporter $monitor,
    ) {}

    public function settle(Order $order): void
    {
        try {
            // ...
        } catch (\Throwable $e) {
            $this->monitor->reportException($e, ['order_id' => $order->getId()]);

            throw $e;
        }
    }
}

Or bind it in services.yaml:

yaml
# config/services.yaml
services:
    App\Service\CheckoutService:
        arguments:
            $monitor: '@monitor.reporter'

The Reporter exposes:

  • reportException(Throwable $e, array $context = []): bool: POST /api/v1/ingest;
  • sendLogs(array $logs): bool: POST /api/v1/logs;
  • sendDependencies(array $packages): bool: POST /api/v1/dependencies.

Each returns false (and never throws) when the client is not configured or the send fails.

Scrubbing sensitive data

Before anything leaves your application, the configured scrub keys are masked recursively in the context array with [scrubbed]. Matching is a case-insensitive substring match on the key name: a configured password also masks user_password. The defaults cover password, passphrase, token, secret, authorization, cookie, referer, referrer and api_key. Add your own keys through the scrub option, see Configuration.

The core adds a second pass the bundle does not configure: values shaped like an email address, an IBAN, a card number, a French social security number or a French phone number are masked wherever they sit, message text and URL segments included, and are named in the mask ([redacted:email]). It is on by default and cannot be turned off from the monitor tree today. See the core's scrubbing page.

Dependency scanning

The bundle does not ship a console command to snapshot your composer.lock (unlike the Laravel SDK). The underlying Reporter::sendDependencies() method exists, and the server's /api/v1/dependencies endpoint is platform-neutral, so you can wire your own console command or CI step that reads composer.lock and posts the package list with your project key. A first-class command may arrive in a future bundle release.

You are reading the Symfony Bundle v1.0 documentation.