Skip to content

Commit 75e60f4

Browse files
committed
feature: allow to configure error handlers in symfony bundle
1 parent 9774381 commit 75e60f4

8 files changed

Lines changed: 922 additions & 28 deletions

File tree

documentation/components/bridges/symfony-telemetry-bundle.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ This bundle integrates Flow PHP's Telemetry library with Symfony applications. I
3030

3131
## Configuration Reference
3232

33+
> Any `error_handler:` field that appears under a provider, processor, or `otlp` exporter references a name from the
34+
> top-level `error_handlers:` map (see [Error Handlers](#error-handlers)). When omitted it defaults to `default`, which
35+
> is auto-created as `{ type: error_log }` if the user does not declare one.
36+
3337
### Resource Configuration
3438

3539
The `resource` node configures OpenTelemetry Resource attributes that identify your service.
@@ -130,6 +134,158 @@ flow_telemetry:
130134
| `baggage` | W3C Baggage only |
131135
| `service` | Custom propagator service |
132136

137+
### Error Handlers
138+
139+
Per the OpenTelemetry spec, the SDK MUST NOT throw to user code at runtime. Errors raised by exporters/processors are
140+
caught and forwarded to a configured error handler. The bundle exposes a **named map** under `error_handlers:` that is
141+
referenced from provider, processor, and OTLP exporter blocks via `error_handler:` fields.
142+
143+
```yaml
144+
flow_telemetry:
145+
error_handlers:
146+
default:
147+
type: error_log # default if omitted
148+
149+
to_file:
150+
type: stream
151+
destination: '%kernel.logs_dir%/flow-telemetry-errors.log'
152+
153+
to_syslog:
154+
type: syslog
155+
facility: local0
156+
severity: warning
157+
158+
fanout:
159+
type: composite
160+
handlers: [default, to_file, to_syslog]
161+
162+
silent:
163+
type: noop
164+
165+
custom:
166+
type: service
167+
service_id: app.my_error_handler
168+
```
169+
170+
If `error_handlers:` is omitted (or `default` is missing inside it), the bundle injects
171+
`error_handlers.default = { type: error_log }` automatically so every `error_handler:` reference always resolves.
172+
173+
Service IDs registered by the bundle (predictable for `decorates:`):
174+
175+
- `flow.telemetry.error_handler.<name>` — e.g. `flow.telemetry.error_handler.default`
176+
177+
#### error_log (default)
178+
179+
Writes formatted Throwables via PHP's `error_log()` — stderr in CLI by default, or the `error_log` ini setting otherwise.
180+
Matches the OTEL spec recommendation to log to standard error output.
181+
182+
| Option | Type | Default | Description |
183+
|-------------------|---------|-------------------|-------------------------------------------------------------------|
184+
| `message_type` | enum | `operating_system` | `operating_system` (0), `email` (1), `file` (3), `sapi` (4) |
185+
| `expand_newlines` | boolean | `false` | Emit one `error_log()` call per line of the formatted message |
186+
| `message_prefix` | string | `[flow-telemetry]` | Prefix prepended to every message |
187+
188+
#### stream
189+
190+
Appends formatted Throwables (one per line) to a file path or `php://` stream wrapper. The handle is opened lazily on
191+
the first call and reused.
192+
193+
| Option | Type | Default | Description |
194+
|----------------------|---------|--------------------|--------------------------------------------------------------|
195+
| `destination` | string | - | File path or `php://stdout`/`php://stderr`/etc. (required) |
196+
| `file_permissions` | integer | `0644` | Permissions for newly created files (ignored for `php://`) |
197+
| `create_directories` | boolean | `true` | Create parent directories of the destination if missing |
198+
| `message_prefix` | string | `[flow-telemetry]` | Prefix prepended to every line |
199+
200+
#### syslog
201+
202+
Writes via `openlog/syslog/closelog`.
203+
204+
| Option | Type | Default | Description |
205+
|------------|---------|------------------|--------------------------------------------------------|
206+
| `ident` | string | `flow-telemetry` | Syslog identity tag |
207+
| `facility` | enum | `user` | RFC 5424 facility (see table below) |
208+
| `log_opts` | integer | `LOG_PID` | Bitmask of `LOG_*` flags passed to `openlog()` |
209+
| `severity` | enum | `error` | RFC 5424 severity (see table below) |
210+
211+
#### udp_syslog
212+
213+
Sends RFC 5424 syslog frames over UDP.
214+
215+
| Option | Type | Default | Description |
216+
|------------|---------|------------------|----------------------------------------------|
217+
| `host` | string | - | Remote syslog host (required) |
218+
| `port` | integer | `514` | Remote syslog port |
219+
| `ident` | string | `flow-telemetry` | Syslog identity tag |
220+
| `facility` | enum | `user` | RFC 5424 facility |
221+
| `severity` | enum | `error` | RFC 5424 severity |
222+
223+
#### composite
224+
225+
Fans an error out to multiple named handlers. Each child invocation is wrapped so a misbehaving handler cannot prevent
226+
siblings from running.
227+
228+
| Option | Type | Default | Description |
229+
|------------|-----------------|---------|------------------------------------------------------------|
230+
| `handlers` | list of strings | - | Names of other entries in `error_handlers:` (required) |
231+
232+
```yaml
233+
fanout:
234+
type: composite
235+
handlers: [default, to_file]
236+
```
237+
238+
#### noop
239+
240+
Discards every Throwable. Intended for tests or explicit silence.
241+
242+
```yaml
243+
silent: { type: noop }
244+
```
245+
246+
#### service
247+
248+
Aliases an existing service that implements `Flow\Telemetry\ErrorHandler\ErrorHandler`.
249+
250+
| Option | Type | Default | Description |
251+
|--------------|--------|---------|----------------------------------------------------------|
252+
| `service_id` | string | - | Service id of the user-provided handler (required) |
253+
254+
```yaml
255+
custom:
256+
type: service
257+
service_id: app.my_error_handler
258+
```
259+
260+
#### Facility values
261+
262+
| Value | Constant |
263+
|-----------|---------------|
264+
| `kernel` | `LOG_KERN` |
265+
| `user` | `LOG_USER` |
266+
| `mail` | `LOG_MAIL` |
267+
| `daemon` | `LOG_DAEMON` |
268+
| `auth` | `LOG_AUTH` |
269+
| `syslog` | `LOG_SYSLOG` |
270+
| `lpr` | `LOG_LPR` |
271+
| `news` | `LOG_NEWS` |
272+
| `uucp` | `LOG_UUCP` |
273+
| `cron` | `LOG_CRON` |
274+
| `local0`..`local7` | `LOG_LOCAL0`..`LOG_LOCAL7` |
275+
276+
#### Severity values
277+
278+
| Value | Constant |
279+
|-------------|---------------|
280+
| `emergency` | `LOG_EMERG` |
281+
| `alert` | `LOG_ALERT` |
282+
| `critical` | `LOG_CRIT` |
283+
| `error` | `LOG_ERR` |
284+
| `warning` | `LOG_WARNING` |
285+
| `notice` | `LOG_NOTICE` |
286+
| `info` | `LOG_INFO` |
287+
| `debug` | `LOG_DEBUG` |
288+
133289
### Exporters (named definitions)
134290

135291
The bundle exposes a **top-level named map** of exporters. The implementation is selected by the **sub-block name**
@@ -167,6 +323,7 @@ Configures the tracer provider for distributed tracing.
167323
```yaml
168324
flow_telemetry:
169325
tracer_provider:
326+
error_handler: default # name from error_handlers; defaults to "default"
170327
sampler:
171328
type: always_on # always_on|always_off|trace_id_ratio|parent_based|service
172329
ratio: 1.0 # Sampling ratio (0.0-1.0, only for trace_id_ratio)
@@ -175,6 +332,7 @@ flow_telemetry:
175332
type: batching # composite|memory|batching|passthrough|void|service
176333
batch_size: 512
177334
exporter: otlp # name of a top-level exporter
335+
error_handler: default
178336
```
179337

180338
**Sampler types:**
@@ -192,22 +350,26 @@ flow_telemetry:
192350
```yaml
193351
flow_telemetry:
194352
meter_provider:
353+
error_handler: default
195354
temporality: cumulative # cumulative|delta
196355
processor:
197356
type: batching
198357
batch_size: 512
199358
exporter: otlp
359+
error_handler: default
200360
```
201361

202362
### LoggerProvider
203363

204364
```yaml
205365
flow_telemetry:
206366
logger_provider:
367+
error_handler: default
207368
processor:
208369
type: batching # composite|memory|batching|passthrough|void|severity_filtering|service
209370
batch_size: 512
210371
exporter: otlp
372+
error_handler: default
211373
```
212374

213375
**Severity filtering** (logs only):
@@ -351,6 +513,7 @@ Sends batches over an embedded transport. The single OTLP exporter handles all t
351513
exporters:
352514
otlp:
353515
otlp:
516+
error_handler: default # name from error_handlers; defaults to "default"
354517
transport:
355518
type: curl
356519
endpoint: 'http://otel-collector:4318'

src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/DependencyInjection/Configuration.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,16 @@ public function getConfigTreeBuilder() : TreeBuilder
144144
->end()
145145
->end()
146146
->end()
147+
->append($this->errorHandlersNode())
147148
->append($this->exportersNode())
148149
->arrayNode('tracer_provider')
149150
->info('TracerProvider configuration. Defaults to void if omitted.')
150151
->addDefaultsIfNotSet()
151152
->children()
153+
->scalarNode('error_handler')
154+
->info('Name of an error_handler entry forwarded to the TracerProvider')
155+
->defaultValue('default')
156+
->end()
152157
->arrayNode('sampler')
153158
->info('Trace sampler configuration')
154159
->addDefaultsIfNotSet()
@@ -176,6 +181,10 @@ public function getConfigTreeBuilder() : TreeBuilder
176181
->info('MeterProvider configuration. Defaults to void if omitted.')
177182
->addDefaultsIfNotSet()
178183
->children()
184+
->scalarNode('error_handler')
185+
->info('Name of an error_handler entry forwarded to the MeterProvider')
186+
->defaultValue('default')
187+
->end()
179188
->enumNode('temporality')
180189
->info('Aggregation temporality')
181190
->values(['cumulative', 'delta'])
@@ -188,6 +197,10 @@ public function getConfigTreeBuilder() : TreeBuilder
188197
->info('LoggerProvider configuration. Defaults to void if omitted.')
189198
->addDefaultsIfNotSet()
190199
->children()
200+
->scalarNode('error_handler')
201+
->info('Name of an error_handler entry forwarded to the LoggerProvider')
202+
->defaultValue('default')
203+
->end()
191204
->append($this->processorNode('log'))
192205
->end()
193206
->end()
@@ -385,6 +398,95 @@ public function getConfigTreeBuilder() : TreeBuilder
385398
return $treeBuilder;
386399
}
387400

401+
private function errorHandlersNode() : ArrayNodeDefinition
402+
{
403+
$builder = new TreeBuilder('error_handlers');
404+
/** @var ArrayNodeDefinition $node */
405+
$node = $builder->getRootNode();
406+
407+
$supportedTypes = ['error_log', 'stream', 'syslog', 'udp_syslog', 'composite', 'noop', 'service'];
408+
$facilities = ['auth', 'cron', 'daemon', 'kernel', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'lpr', 'mail', 'news', 'syslog', 'user', 'uucp'];
409+
$severities = ['alert', 'critical', 'debug', 'emergency', 'error', 'info', 'notice', 'warning'];
410+
$messageTypes = ['operating_system', 'email', 'file', 'sapi'];
411+
412+
$node
413+
->info('Named error handler definitions referenced by providers, processors, and OTLP exporters via "error_handler:" fields. If "default" is omitted it is auto-created with type: error_log.')
414+
->useAttributeAsKey('name')
415+
->arrayPrototype()
416+
->children()
417+
->enumNode('type')
418+
->values($supportedTypes)
419+
->defaultValue('error_log')
420+
->end()
421+
->enumNode('message_type')
422+
->info('error_log message type (only for type: error_log)')
423+
->values($messageTypes)
424+
->defaultValue('operating_system')
425+
->end()
426+
->booleanNode('expand_newlines')
427+
->info('Emit one error_log() call per line (only for type: error_log)')
428+
->defaultFalse()
429+
->end()
430+
->scalarNode('message_prefix')
431+
->info('Prefix prepended to each formatted Throwable (error_log + stream)')
432+
->defaultValue('[flow-telemetry]')
433+
->end()
434+
->scalarNode('destination')
435+
->info('File path or php:// stream URI (required for type: stream)')
436+
->defaultNull()
437+
->end()
438+
->integerNode('file_permissions')
439+
->info('Permissions applied when creating new files (only for type: stream)')
440+
->defaultValue(0644)
441+
->min(0)
442+
->max(0777)
443+
->end()
444+
->booleanNode('create_directories')
445+
->info('Create parent directories of the destination if they do not exist (only for type: stream)')
446+
->defaultTrue()
447+
->end()
448+
->scalarNode('ident')
449+
->info('Syslog identity tag (syslog + udp_syslog)')
450+
->defaultValue('flow-telemetry')
451+
->end()
452+
->enumNode('facility')
453+
->info('Syslog facility (syslog + udp_syslog)')
454+
->values($facilities)
455+
->defaultValue('user')
456+
->end()
457+
->integerNode('log_opts')
458+
->info('Bitmask of LOG_* options passed to openlog() (only for type: syslog)')
459+
->defaultValue(\LOG_PID)
460+
->end()
461+
->enumNode('severity')
462+
->info('Syslog severity (syslog + udp_syslog)')
463+
->values($severities)
464+
->defaultValue('error')
465+
->end()
466+
->scalarNode('host')
467+
->info('Remote syslog host (required for type: udp_syslog)')
468+
->defaultNull()
469+
->end()
470+
->integerNode('port')
471+
->info('Remote syslog port (only for type: udp_syslog)')
472+
->defaultValue(514)
473+
->min(1)
474+
->max(65535)
475+
->end()
476+
->arrayNode('handlers')
477+
->info('Named error_handler entries fanned-out to (only for type: composite)')
478+
->scalarPrototype()->end()
479+
->end()
480+
->scalarNode('service_id')
481+
->info('Custom error handler service ID (only for type: service)')
482+
->defaultNull()
483+
->end()
484+
->end()
485+
->end();
486+
487+
return $node;
488+
}
489+
388490
private function exportersNode() : ArrayNodeDefinition
389491
{
390492
$builder = new TreeBuilder('exporters');
@@ -463,6 +565,10 @@ private function innerProcessorNode(string $signalType) : ArrayNodeDefinition
463565
->info('Custom processor service ID (only for type: service)')
464566
->defaultNull()
465567
->end()
568+
->scalarNode('error_handler')
569+
->info('Name of an error_handler entry forwarded to the inner processor')
570+
->defaultValue('default')
571+
->end()
466572
->end();
467573

468574
return $node;
@@ -482,6 +588,10 @@ private function otlpExporterNode() : ArrayNodeDefinition
482588
->thenInvalid('OTLP exporter requires a "transport" configuration block.')
483589
->end()
484590
->children()
591+
->scalarNode('error_handler')
592+
->info('Name of an error_handler entry forwarded to the OTLP exporter')
593+
->defaultValue('default')
594+
->end()
485595
->append($this->transportNode())
486596
->end();
487597

@@ -528,6 +638,10 @@ private function processorNode(string $signalType) : ArrayNodeDefinition
528638
->values(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])
529639
->defaultValue('info')
530640
->end()
641+
->scalarNode('error_handler')
642+
->info('Name of an error_handler entry forwarded to this processor')
643+
->defaultValue('default')
644+
->end()
531645
->arrayNode('processors')
532646
->info('Array of processor configurations (only for type: composite)')
533647
->arrayPrototype()
@@ -550,6 +664,10 @@ private function processorNode(string $signalType) : ArrayNodeDefinition
550664
->values(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])
551665
->defaultValue('info')
552666
->end()
667+
->scalarNode('error_handler')
668+
->info('Name of an error_handler entry forwarded to this child processor')
669+
->defaultValue('default')
670+
->end()
553671
->append($this->innerProcessorNode($signalType))
554672
->end()
555673
->end()

0 commit comments

Comments
 (0)