PHPackages                             sauron/symfony-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. sauron/symfony-bundle

ActiveSymfony-bundle[Logging &amp; Monitoring](/categories/logging)

sauron/symfony-bundle
=====================

Symfony bundle that instruments your application and sends spans to Sauron APM

v0.1.0(3mo ago)00MITPHPPHP &gt;=8.1

Since Mar 26Pushed 3mo agoCompare

[ Source](https://github.com/p0lemic/sauron-symfony-bundle)[ Packagist](https://packagist.org/packages/sauron/symfony-bundle)[ RSS](/packages/sauron-symfony-bundle/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

Sauron Symfony Bundle
=====================

[](#sauron-symfony-bundle)

Sauron is a transparent HTTP proxy and APM dashboard for API observability. This bundle instruments Symfony applications by reading the `traceparent` header the Sauron proxy injects and reporting `controller` and `db` spans to the dashboard — giving you end-to-end trace waterfalls with zero manual code changes.

Architecture
------------

[](#architecture)

```
Browser / API client
        │
        ▼
Sauron proxy  :8080   ──────────────────────────▶  Symfony app  :9000
  (profiler)          injects traceparent header     (your app)
        │                                                 │
        │  stores proxy spans                             │  kernel.terminate
        ▼                                                 ▼
  profiler.db  ◀─────────── shared storage ─────  Sauron dashboard  :9090
  (SQLite/PG)                                       POST /ingest/spans

```

Traffic goes through the Sauron proxy, which records latency, status codes, and injects a W3C `traceparent` header. The bundle picks up that header inside Symfony and reports child spans (controller timing, DB queries) back to the dashboard after the response is sent.

Installation
------------

[](#installation)

```
composer require sauron/symfony-bundle
```

Register the bundle in `config/bundles.php`:

```
return [
    // ...
    Sauron\Bundle\SauronBundle::class => ['all' => true],
];
```

Create `config/packages/sauron.yaml`:

```
sauron:
  enabled: true
  endpoint: '%env(SAURON_ENDPOINT)%'
  service_name: '%env(APP_NAME)%'
```

Add to `.env`:

```
SAURON_ENDPOINT=http://localhost:9090/ingest/spans
APP_NAME=my-symfony-app

```

That's it. Requests routed through the Sauron proxy will now appear as full trace waterfalls in the dashboard.

---

Prerequisites
-------------

[](#prerequisites)

- PHP ≥ 8.1, Symfony ≥ 6.0
- Sauron binaries built from `cmd/profiler` and `cmd/dashboard` (see Step 1)
- `ext-curl` or `allow_url_fopen=1` for flushing spans
- (Optional) `doctrine/dbal` ≥ 3.0 and DoctrineBundle for automatic DB query spans

---

Step-by-step integration
------------------------

[](#step-by-step-integration)

### Step 1 — Build Sauron binaries

[](#step-1--build-sauron-binaries)

```
go build -o profiler ./cmd/profiler
go build -o dashboard ./cmd/dashboard
```

### Step 2 — Start the proxy and dashboard

[](#step-2--start-the-proxy-and-dashboard)

Point the proxy at your Symfony app and use a shared storage file:

```
./profiler --upstream http://localhost:9000 --port 8080 --storage-dsn profiler.db
./dashboard --listen :9090 --storage-dsn profiler.db
```

**`profiler` key flags**

FlagDefaultDescription`--upstream`*(required)*Symfony app base URL, e.g. `http://localhost:9000``--port``8080`Proxy listen port`--storage-driver``sqlite``sqlite` or `postgres``--storage-dsn``profiler.db`File path (SQLite) or connection string (Postgres)`--retention`disabledHow long to keep records, e.g. `7d`, `24h``--no-trace-context`falseDisable W3C TraceContext header injection`--tls-skip-verify`falseDisable TLS certificate verification for upstream`--timeout``30s`Upstream request timeout`--config`—Path to YAML config file**`dashboard` key flags**

FlagDefaultDescription`--listen``:9090`Dashboard listen address`--storage-driver``sqlite``sqlite` or `postgres``--storage-dsn``profiler.db`Must match the profiler's DSN`--metrics-window``30m`Aggregation window for metrics`--apdex-t``500`Apdex satisfaction threshold (ms)`--error-rate-threshold`disabledError rate % to trigger alert, e.g. `10.0``--throughput-drop-threshold`disabledMin RPS % of baseline before alerting, e.g. `50.0``--baseline-windows``5`Past windows used for baseline`--anomaly-threshold``3.0`Anomaly detection multiplier`--webhook-url`—URL to POST alert notifications to### Step 3 — Install the bundle

[](#step-3--install-the-bundle)

```
composer require sauron/symfony-bundle
```

### Step 4 — Register the bundle

[](#step-4--register-the-bundle)

Add to `config/bundles.php`:

```
return [
    // ...
    Sauron\Bundle\SauronBundle::class => ['all' => true],
];
```

### Step 5 — Create `config/packages/sauron.yaml`

[](#step-5--create-configpackagessauronyaml)

```
sauron:
  enabled: true
  endpoint: '%env(SAURON_ENDPOINT)%'
  service_name: '%env(APP_NAME)%'
  instrument_doctrine: true
  timeout_ms: 2000
```

### Step 6 — Add environment variables

[](#step-6--add-environment-variables)

Add to `.env`:

```
SAURON_ENDPOINT=http://localhost:9090/ingest/spans
APP_NAME=my-symfony-app

```

### Step 7 — Verify

[](#step-7--verify)

Send a request through the proxy and check the dashboard:

```
curl http://localhost:8080/api/your-endpoint
# Open http://localhost:9090 → Traces → click the trace → see waterfall
```

---

How it works
------------

[](#how-it-works)

The bundle registers a `TraceSubscriber` that hooks into the Symfony kernel event lifecycle:

EventAction`kernel.controller`Reads `traceparent`, generates controller span ID, starts timer`kernel.response`Closes controller span, records HTTP method, route, status code`kernel.exception`Marks the active span as `status=error``kernel.terminate` (priority −1024)Flushes all spans in a single POST to `endpoint`The flush happens in `kernel.terminate`, **after** the response is sent to the client — so there is zero latency impact on your users.

The Doctrine middleware wraps the DBAL driver and records one `db` span per query, parented to the active controller span.

### Span hierarchy in the waterfall

[](#span-hierarchy-in-the-waterfall)

```
HTTP proxy span (created by Sauron proxy)
└── controller  App\Controller\UserController::index   140ms
    ├── db  SELECT users                                  8ms
    ├── db  SELECT orders WHERE user_id = ?              12ms
    └── db  INSERT audit_log                              3ms

```

---

Configuration reference
-----------------------

[](#configuration-reference)

KeyTypeDefaultDescription`enabled`bool`true`Set `false` to disable all instrumentation`endpoint`string`http://localhost:9090/ingest/spans`Dashboard ingest URL`service_name`string`symfony-app`Label shown in the Traces UI`instrument_doctrine`bool`true`Auto-instrument Doctrine DBAL queries`timeout_ms`int`2000`Max milliseconds to wait when flushing spans---

Doctrine instrumentation
------------------------

[](#doctrine-instrumentation)

- Requires `doctrine/dbal` ^3.0|^4.0 and DoctrineBundle to be configured
- The bundle auto-registers a `doctrine.middleware` tag — no manual wiring needed
- Each SQL query becomes a `db` span, child of the `controller` span
- SQL is normalized (literals replaced with `?`) before storage — safe to log

---

Adding custom spans
-------------------

[](#adding-custom-spans)

Inject `SauronClient` and call `recordSpan()` anywhere in your application:

```
use Sauron\Bundle\SauronClient;

class MyService
{
    public function __construct(private readonly SauronClient $client) {}

    public function doWork(): void
    {
        $start = microtime(true);

        // ... do work ...

        $this->client->recordSpan(
            name:       'my-service.doWork',
            kind:       'rpc',                        // controller|db|cache|event|view|rpc
            startTime:  new \DateTimeImmutable(),
            durationMs: (microtime(true) - $start) * 1000,
            attributes: ['key' => 'value'],
            status:     'ok',
        );
    }
}
```

---

Disabling per environment
-------------------------

[](#disabling-per-environment)

```
# config/packages/test/sauron.yaml
sauron:
  enabled: false
```

---

Using PostgreSQL instead of SQLite
----------------------------------

[](#using-postgresql-instead-of-sqlite)

Both binaries must point to the same database:

```
./profiler --storage-driver postgres \
           --storage-dsn "postgres://user:pass@localhost:5432/sauron" \
           --upstream http://localhost:9000

./dashboard --storage-driver postgres \
            --storage-dsn "postgres://user:pass@localhost:5432/sauron"
```

---

Troubleshooting
---------------

[](#troubleshooting)

SymptomLikely causeFixNo spans in dashboard`traceparent` not injectedRequests must go through the proxy, not directly to Symfony`container.xml` compile errorDI bug in old bundle versionUpgrade or apply the `Reference` fix in `SauronExtension.php`Doctrine spans missing`instrument_doctrine: false` or DBAL not installedEnable config key; `composer require doctrine/dbal`Spans appear but dashboard shows nothingShared `--storage-dsn` mismatchBoth binaries must point to the same `profiler.db`High latency impact`timeout_ms` too largeReduce to `500`; flush is async (happens in `kernel.terminate`, after response is sent)Only controller spans, no DB spansMissing DoctrineBundle 2.x+The `doctrine.middleware` tag requires DoctrineBundle ≥ 2.0

###  Health Score

31

—

LowBetter than 66% of packages

Maintenance82

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity32

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

91d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4ebfa302c0dfe1d8950e9195d182779cbc2cb810091b9ecdc12258acd9db06a?d=identicon)[p0lemic](/maintainers/p0lemic)

---

Top Contributors

[![p0lemic](https://avatars.githubusercontent.com/u/2384059?v=4)](https://github.com/p0lemic "p0lemic (3 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/sauron-symfony-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/sauron-symfony-bundle/health.svg)](https://phpackages.com/packages/sauron-symfony-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.5M374](/packages/easycorp-easyadmin-bundle)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1155.2k](/packages/rcsofttech-audit-trail-bundle)[inspector-apm/inspector-symfony

Code Execution Monitoring for Symfony applications.

2836.4k9](/packages/inspector-apm-inspector-symfony)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1615.6k12](/packages/2lenet-crudit-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
