Skip to content

Commit 05c74e8

Browse files
authored
Merge pull request #2346 from flow-php/psr-3-telemetry-bridge
Psr 3 telemetry bridge
2 parents 5708276 + 4b4cf7d commit 05c74e8

47 files changed

Lines changed: 1893 additions & 23 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codecov.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ component_management:
9999
name: bridge-psr18-telemetry
100100
paths:
101101
- src/bridge/psr18/telemetry/**
102+
- component_id: bridge-psr3-telemetry
103+
name: bridge-psr3-telemetry
104+
paths:
105+
- src/bridge/psr3/telemetry/**
102106
- component_id: bridge-psr7-telemetry
103107
name: bridge-psr7-telemetry
104108
paths:

.github/workflows/monorepo-split.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ jobs:
8888
split_repository: 'openapi-specification-bridge'
8989
- local_path: 'src/bridge/postgresql/valinor'
9090
split_repository: 'postgresql-valinor-bridge'
91+
- local_path: 'src/bridge/psr3/telemetry'
92+
split_repository: 'psr3-telemetry-bridge'
9193
- local_path: 'src/bridge/psr7/telemetry'
9294
split_repository: 'psr7-telemetry-bridge'
9395
- local_path: 'src/bridge/psr18/telemetry'

composer.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"flow-php/parquet-viewer": "self.version",
107107
"flow-php/postgresql": "self.version",
108108
"flow-php/postgresql-valinor-bridge": "self.version",
109+
"flow-php/psr3-telemetry-bridge": "self.version",
109110
"flow-php/psr7-telemetry-bridge": "self.version",
110111
"flow-php/psr18-telemetry-bridge": "self.version",
111112
"flow-php/snappy": "self.version",
@@ -150,6 +151,7 @@
150151
"src/bridge/monolog/telemetry/src/Flow",
151152
"src/bridge/openapi/specification/src/Flow",
152153
"src/bridge/postgresql/valinor/src/Flow",
154+
"src/bridge/psr3/telemetry/src/Flow",
153155
"src/bridge/psr7/telemetry/src/Flow",
154156
"src/bridge/psr18/telemetry/src/Flow",
155157
"src/bridge/symfony/filesystem-bundle/src/Flow",
@@ -208,6 +210,7 @@
208210
"src/bridge/phpunit/postgresql/src/Flow/Bridge/PHPUnit/PostgreSQL/DSL/functions.php",
209211
"src/bridge/openapi/specification/src/Flow/Bridge/OpenAPI/Specification/DSL/functions.php",
210212
"src/bridge/postgresql/valinor/src/Flow/PostgreSql/Bridge/Valinor/DSL/functions.php",
213+
"src/bridge/psr3/telemetry/src/Flow/Bridge/Psr3/Telemetry/DSL/functions.php",
211214
"src/bridge/psr7/telemetry/src/Flow/Bridge/Psr7/Telemetry/DSL/functions.php",
212215
"src/bridge/psr18/telemetry/src/Flow/Bridge/Psr18/Telemetry/DSL/functions.php",
213216
"src/bridge/symfony/http-foundation-telemetry/src/Flow/Bridge/Symfony/HttpFoundationTelemetry/DSL/functions.php",
@@ -259,6 +262,7 @@
259262
"src/bridge/monolog/telemetry/tests/Flow",
260263
"src/bridge/openapi/specification/tests/Flow",
261264
"src/bridge/postgresql/valinor/tests/Flow",
265+
"src/bridge/psr3/telemetry/tests/Flow",
262266
"src/bridge/psr7/telemetry/tests/Flow",
263267
"src/bridge/psr18/telemetry/tests/Flow",
264268
"src/bridge/symfony/filesystem-bundle/tests/Flow",
@@ -375,6 +379,8 @@
375379
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.parquet.xml",
376380
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.parquet-viewer.xml",
377381
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.postgresql.xml",
382+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.postgresql-migrations.xml",
383+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.rdsl.xml",
378384
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.snappy.xml",
379385
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.telemetry.xml",
380386
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/lib.types.xml",
@@ -394,9 +400,22 @@
394400
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.filesystem.async-aws.xml",
395401
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.filesystem.azure.xml",
396402
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.monolog.http.xml",
403+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.monolog-telemetry.xml",
397404
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.openapi.specification.xml",
405+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.phpunit.postgresql.xml",
406+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.phpunit.telemetry.xml",
398407
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.postgresql.valinor.xml",
399-
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.http-foundation.xml"
408+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.psr3.telemetry.xml",
409+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.psr7.telemetry.xml",
410+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.psr18.telemetry.xml",
411+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.filesystem.xml",
412+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.filesystem-cache.xml",
413+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.http-foundation.xml",
414+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.postgresql-cache.xml",
415+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.postgresql-messenger.xml",
416+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.postgresql-migrations.xml",
417+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.postgresql-session.xml",
418+
"./tools/phpdocumentor/vendor/bin/phpdoc --config=./phpdoc/bridge.symfony.telemetry.xml"
400419
],
401420
"build:parquet:thrift": [
402421
"grep -q 'namespace php Flow.Parquet.ThriftModel' src/lib/parquet/src/Flow/Parquet/Resources/Thrift/parquet.thrift || { echo \"Flow php namespace not found in thrift definition!\"; exit 1; }\n",

composer.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# PSR-3 Telemetry Bridge
2+
3+
Flow PSR-3 Telemetry Bridge exposes a [PSR-3](https://www.php-fig.org/psr/psr-3/) `LoggerInterface` implementation
4+
backed by Flow PHP Telemetry. Any framework or library that depends on `Psr\Log\LoggerInterface` can write through this
5+
adapter and have its records emitted as Telemetry log signals.
6+
7+
- [⬅️️ Back](/documentation/introduction.md)
8+
- [📦Packagist](https://packagist.org/packages/flow-php/psr3-telemetry-bridge)
9+
- [➡️ Installation](/documentation/installation/packages/psr3-telemetry-bridge.md)
10+
- [🐙GitHub](https://github.com/flow-php/psr3-telemetry-bridge)
11+
- [📚API Reference](/documentation/api/bridge/psr3/telemetry)
12+
- [🗺DSL](/documentation/api/bridge/psr3/telemetry/namespaces/flow-bridge-psr3-telemetry-dsl.html)
13+
14+
[TOC]
15+
16+
## Installation
17+
18+
For detailed installation instructions, see the [installation page](/documentation/installation/packages/psr3-telemetry-bridge.md).
19+
20+
## Overview
21+
22+
This bridge wraps `Flow\Telemetry\Logger\Logger` behind a PSR-3 `LoggerInterface`. It:
23+
24+
- Maps PSR-3 log levels to Telemetry severities (8 → 6, customizable).
25+
- Performs PSR-3 `{placeholder}` interpolation against the context array (scalars and `Stringable` only).
26+
- Stores every context entry as a log record attribute under its raw key.
27+
- Routes a `Throwable` under the `exception` key through `LogRecord::setException()`, populating
28+
`exception.type`, `exception.message`, and `exception.stacktrace`.
29+
- Throws `Psr\Log\InvalidArgumentException` when `log()` is called with an unknown level, per spec.
30+
31+
## Basic Usage
32+
33+
```php
34+
<?php
35+
36+
use function Flow\Bridge\Psr3\Telemetry\DSL\psr3_telemetry_logger;
37+
use function Flow\Telemetry\DSL\telemetry;
38+
39+
$telemetry = telemetry($resource);
40+
$psrLogger = psr3_telemetry_logger($telemetry->logger('my-service'));
41+
42+
$psrLogger->info('User {user_id} logged in', ['user_id' => 123]);
43+
// → body: 'User 123 logged in'
44+
// → severity: INFO
45+
// → attributes: {user_id: 123}
46+
```
47+
48+
The returned `TelemetryLogger` is a drop-in `Psr\Log\LoggerInterface`, so it can be passed to anything that accepts a
49+
PSR-3 logger (Symfony, Laravel, third-party libraries).
50+
51+
## Severity Mapping
52+
53+
The default mapping collapses PSR-3's eight levels onto Telemetry's six OTEL-aligned severities:
54+
55+
| PSR-3 Level | Telemetry Severity |
56+
|-------------|--------------------|
57+
| `debug` | `DEBUG` |
58+
| `info` | `INFO` |
59+
| `notice` | `INFO` |
60+
| `warning` | `WARN` |
61+
| `error` | `ERROR` |
62+
| `critical` | `FATAL` |
63+
| `alert` | `FATAL` |
64+
| `emergency` | `FATAL` |
65+
66+
Override the mapping by passing a custom `psr3_severity_mapper()` to `psr3_log_record_converter()`:
67+
68+
```php
69+
<?php
70+
71+
use Flow\Telemetry\Logger\Severity;
72+
use Psr\Log\LogLevel;
73+
use function Flow\Bridge\Psr3\Telemetry\DSL\{psr3_log_record_converter, psr3_severity_mapper, psr3_telemetry_logger};
74+
75+
$converter = psr3_log_record_converter(
76+
severityMapper: psr3_severity_mapper([
77+
LogLevel::DEBUG => Severity::TRACE,
78+
LogLevel::INFO => Severity::INFO,
79+
LogLevel::NOTICE => Severity::WARN,
80+
LogLevel::WARNING => Severity::WARN,
81+
LogLevel::ERROR => Severity::ERROR,
82+
LogLevel::CRITICAL => Severity::FATAL,
83+
LogLevel::ALERT => Severity::FATAL,
84+
LogLevel::EMERGENCY => Severity::FATAL,
85+
]),
86+
);
87+
88+
$psrLogger = psr3_telemetry_logger($telemetry->logger('my-service'), $converter);
89+
```
90+
91+
## Context Handling
92+
93+
### Attributes
94+
95+
Every context entry becomes an attribute on the emitted `LogRecord`, keyed verbatim:
96+
97+
```php
98+
$psrLogger->info('Request processed', [
99+
'http.method' => 'GET',
100+
'http.status' => 200,
101+
'duration_ms' => 12.4,
102+
]);
103+
// attributes: {http.method: 'GET', http.status: 200, duration_ms: 12.4}
104+
```
105+
106+
Non-scalar values are normalized via `ValueNormalizer`:
107+
108+
- `null``'null'`
109+
- `DateTimeInterface`, `Throwable` → passed through
110+
- arrays → recursively normalized
111+
- objects with `__toString()` → string cast
112+
- objects without `__toString()` → class name
113+
- other types → `get_debug_type()` result
114+
115+
### Exceptions
116+
117+
A `Throwable` under the `exception` key is unpacked into the standard OTEL exception attributes via
118+
`LogRecord::setException()`:
119+
120+
```php
121+
try {
122+
// ...
123+
} catch (\Throwable $e) {
124+
$psrLogger->error('Operation failed', ['exception' => $e]);
125+
}
126+
// attributes:
127+
// exception.type: 'RuntimeException'
128+
// exception.message: '...'
129+
// exception.stacktrace: '...'
130+
```
131+
132+
The original `exception` key is **not** stored as a separate attribute. A non-`Throwable` value under `exception` is
133+
treated as a regular attribute.
134+
135+
### Message Interpolation
136+
137+
Per PSR-3 §1.2, `{placeholder}` tokens in the message body are substituted with matching context entries. Only scalars
138+
and `Stringable` objects participate; arrays, `Throwable` instances, and plain objects are left in the template. The
139+
context entries remain available as attributes regardless.
140+
141+
```php
142+
$psrLogger->warning('Quota exceeded for {tenant} ({used}/{limit})', [
143+
'tenant' => 'acme',
144+
'used' => 105,
145+
'limit' => 100,
146+
]);
147+
// body: 'Quota exceeded for acme (105/100)'
148+
// attributes: {tenant: 'acme', used: 105, limit: 100}
149+
```
150+
151+
## DSL Functions
152+
153+
### psr3_telemetry_logger()
154+
155+
Wraps a Flow Telemetry `Logger` in a PSR-3 `LoggerInterface`.
156+
157+
```php
158+
$psrLogger = psr3_telemetry_logger(
159+
logger: $telemetry->logger('my-service'),
160+
converter: $converter, // optional, defaults to LogRecordConverter()
161+
);
162+
```
163+
164+
### psr3_log_record_converter()
165+
166+
Builds the converter that turns each PSR-3 call into a `LogRecord`. Use it to plug in a custom severity mapper or value
167+
normalizer.
168+
169+
```php
170+
$converter = psr3_log_record_converter(
171+
severityMapper: psr3_severity_mapper([...]),
172+
valueNormalizer: psr3_value_normalizer(),
173+
);
174+
```
175+
176+
### psr3_severity_mapper()
177+
178+
Builds a `SeverityMapper`. Pass `null` (default) to use the standard PSR-3 → OTEL mapping, or a full PSR-3 → Severity
179+
array to override.
180+
181+
```php
182+
$mapper = psr3_severity_mapper([
183+
LogLevel::DEBUG => Severity::TRACE,
184+
// ... rest of PSR-3 levels
185+
]);
186+
```
187+
188+
### psr3_value_normalizer()
189+
190+
Builds the default `ValueNormalizer`. Provided for symmetry with the other DSL helpers; most users won't need it.
191+
192+
```php
193+
$normalizer = psr3_value_normalizer();
194+
```

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ flow_telemetry:
4242
static:
4343
cache:
4444
enabled: true # Cache static attributes (default: true)
45-
path: null # Cache path (default: kernel cache dir)
45+
path: null # Cache file path (default: sys_get_temp_dir()/flow_telemetry_resource.cache)
4646
os:
4747
enabled: true # Detect os.type, os.name, os.version, os.description
4848
host:
@@ -75,6 +75,12 @@ flow_telemetry:
7575

7676
Static detectors are cached by default. Dynamic detectors run on every request/command.
7777

78+
The cache file lives outside Symfony's cache lifecycle on purpose: building the Symfony cache
79+
(via `cache:warmup`) at image build time would otherwise freeze runtime-dependent attributes
80+
such as `host.name` or `process.pid` from the build container. Defaulting to
81+
`sys_get_temp_dir()` keeps the cache per-runtime and avoids that pitfall. To invalidate it,
82+
delete the cache file or restart the process; `cache:clear` does not touch it.
83+
7884
Custom attributes override auto-detected values.
7985

8086
### Clock Configuration
@@ -660,6 +666,33 @@ flow_telemetry:
660666
version: '1.0.0' # default: 'unknown'
661667
```
662668

669+
### Main Logger
670+
671+
The bundle depends on [PSR-3 Telemetry Bridge](/documentation/components/bridges/psr3-telemetry-bridge.md) and registers a PSR-3 wrapper service for every named Telemetry logger at `flow.telemetry.<name>.logger.psr3`. This makes Flow Telemetry loggers usable as Symfony's `logger` service, removing the need for Monolog when telemetry is the only logging destination.
672+
673+
In addition, the bundle always registers a `default` logger, meter, and tracer — `flow.telemetry.default.logger`, `flow.telemetry.default.logger.psr3`, `flow.telemetry.default.meter`, `flow.telemetry.default.tracer` — regardless of what is configured under `loggers`/`meters`/`tracers`. Defining your own `default` entry under those keys is allowed and will override the auto-default.
674+
675+
**Options:**
676+
677+
| Option | Type | Default | Description |
678+
|--------------------|------------------|---------|--------------------------------------------------------------------------------------------------------------|
679+
| `framework_logger` | `string \| null` | `null` | Name of a logger configured under `loggers` (or the always-available `default`) to alias as Symfony `logger` |
680+
681+
**Behavior:**
682+
683+
- When `framework_logger` is set, the bundle aliases the Symfony `logger` service to `flow.telemetry.<framework_logger>.logger.psr3`. If no logger with that name exists, container compilation fails with a clear error.
684+
- When `framework_logger` is `null` and Symfony's `logger` service is the default `Symfony\Component\HttpKernel\Log\Logger`, the bundle automatically aliases `logger` to `flow.telemetry.default.logger.psr3`.
685+
- When `framework_logger` is `null` and `logger` is provided by another bundle (Monolog, custom alias, etc.), the bundle leaves `logger` alone.
686+
687+
```yaml
688+
flow_telemetry:
689+
loggers:
690+
app:
691+
version: '1.0.0'
692+
693+
framework_logger: app # Symfony "logger" service -> flow.telemetry.app.logger.psr3
694+
```
695+
663696
## Pattern Matching
664697

665698
Several configuration options support pattern matching for exclusion lists (paths, commands, templates, etc.).
@@ -840,6 +873,22 @@ flow_telemetry:
840873
transport:
841874
endpoint: '%env(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)%'
842875
876+
loggers:
877+
app:
878+
version: '%env(APP_VERSION)%'
879+
audit:
880+
version: '%env(APP_VERSION)%'
881+
attributes:
882+
channel: audit
883+
meters:
884+
business:
885+
version: '%env(APP_VERSION)%'
886+
tracers:
887+
checkout:
888+
version: '%env(APP_VERSION)%'
889+
890+
framework_logger: app # Symfony "logger" service -> flow.telemetry.app.logger.psr3
891+
843892
instrumentation:
844893
http_kernel:
845894
enabled: true
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
seo_title: "Installing PSR-3 Telemetry Bridge"
3+
seo_description: >
4+
How to install flow-php/psr3-telemetry-bridge in your PHP project using Composer.
5+
---
6+
7+
# PSR-3 Telemetry Bridge
8+
9+
- [⬅️️ Back](/documentation/installation.md)
10+
- [📜 Documentation](/documentation/components/bridges/psr3-telemetry-bridge.md)
11+
- [📦 Packagist](https://packagist.org/packages/flow-php/psr3-telemetry-bridge)
12+
13+
[TOC]
14+
15+
## Composer
16+
17+
```bash
18+
composer require flow-php/psr3-telemetry-bridge:~--FLOW_PHP_VERSION--
19+
```
20+
21+
## Core Dependencies
22+
23+
- [psr/log](https://packagist.org/packages/psr/log)
24+
- [flow-php/telemetry](https://packagist.org/packages/flow-php/telemetry)

0 commit comments

Comments
 (0)