Skip to content

Commit ad225bf

Browse files
authored
Monolog telemetry bridge message placeholders (#2477)
* feature: interpolate {placeholder} tokens in telemetry log messages - Monolog Bridge - PSR3 Bridge * refactor(flow-php/symfony-postgresql-bundle): migrations target a single configured connectio - add `migrations.connection` config (defaults to the first connection) - remove the `--connection` (`-c`) override from migration commands - register migration services under connection-less ids; inject Migrator/Store/Configuration directly into commands and the profiler collector instead of the service locator - migrations profiler panel reports only the configured connection * fix(flow-php/symfony-telemetry-bundle): run DBALTelemetryPass before DoctrineBundle's MiddlewaresPas
1 parent 2ddd79e commit ad225bf

52 files changed

Lines changed: 1110 additions & 867 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.

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ flow_postgresql:
150150
profiler: false # do not record queries in selected connection (default: true)
151151
```
152152

153-
When `migrations` are enabled, a separate **Flow Migrations** panel reports each connection's
153+
When `migrations` are enabled, a separate **Flow Migrations** panel reports the migrations connection's
154154
executed, pending and unavailable migrations (with execution time) — like the Doctrine Migrations
155155
bundle's panel. It queries the database on every profiled request; set `profiler.migrations: false`
156156
to disable it while keeping the query panel:
@@ -163,8 +163,9 @@ flow_postgresql:
163163

164164
### Migrations
165165

166-
Migrations are configured at the top level, not per connection. Use `--connection` to target a specific connection
167-
when running migration commands.
166+
Migrations are configured at the top level, not per connection, and always run against a single connection.
167+
Set `connection` to choose which one; when omitted it defaults to the default (first) connection. Every migration
168+
command operates on that connection — there is no per-command connection override.
168169

169170
```yaml
170171
flow_postgresql:
@@ -174,6 +175,7 @@ flow_postgresql:
174175
175176
migrations:
176177
enabled: true
178+
connection: ~ # Connection migrations run against (default: the first/default connection)
177179
directory: "%kernel.project_dir%/migrations" # Where migration files are stored
178180
namespace: "App\\Migrations" # PHP namespace for generated migrations
179181
table_name: "flow_migrations" # Database table tracking executed migrations
@@ -418,7 +420,8 @@ These commands are always available, regardless of migration configuration.
418420
| `flow:database:drop` | Drop the configured database (requires `--force`) |
419421
| `flow:sql:run` | Execute SQL directly on the database |
420422

421-
All commands accept `--connection` (`-c`) to target a specific connection.
423+
The commands above accept `--connection` (`-c`) to target a specific connection. Migration commands do not —
424+
they always run against the configured migrations connection.
422425

423426
### Migration Commands
424427

@@ -440,7 +443,6 @@ These commands are available when `migrations.enabled: true` for at least one co
440443

441444
| Option | Description |
442445
|-----------------------|-------------------------------------------------------|
443-
| `--connection` (`-c`) | Target a specific connection |
444446
| `--dry-run` | Preview changes without applying (migrate, execute) |
445447
| `--all-or-nothing` | Wrap all migrations in a single transaction (migrate) |
446448
| `--up` / `--down` | Migration direction (execute) |

documentation/upgrading.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,23 @@ Handle failures via the `ErrorHandler` (default `ErrorLogHandler`) instead of tr
9999
bundle injects the exporter's configured `error_handler` into the transport automatically. Failover behavior
100100
(`FailoverTransportException`) is unchanged.
101101

102+
### 7) `flow-php/symfony-postgresql-bundle` - migrations run against a single configured connection
103+
104+
| Before | After |
105+
|---------------------------------------------------------|----------------------------------------------------------------------------------|
106+
| `flow:migrations:* --connection=<name>` (`-c`) | removed — every migration command uses the configured migrations connection |
107+
| migrator stack registered for every connection | registered only for the migrations connection |
108+
|| `flow_postgresql.migrations.connection: <name>` (defaults to the first connection)|
109+
110+
To run migrations against a non-default connection, set `migrations.connection` instead of passing `-c`:
111+
112+
```yaml
113+
flow_postgresql:
114+
migrations:
115+
enabled: true
116+
connection: analytics
117+
```
118+
102119
---
103120
104121
## Upgrading from 0.39.x to 0.40.x

src/bridge/monolog/telemetry/src/Flow/Bridge/Monolog/Telemetry/LogRecordConverter.php

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@
44

55
namespace Flow\Bridge\Monolog\Telemetry;
66

7+
use BackedEnum;
8+
use DateTimeInterface;
79
use Flow\Telemetry\Logger\LogRecord as TelemetryLogRecord;
810
use Monolog\LogRecord;
911
use Throwable;
12+
use UnitEnum;
13+
14+
use function array_keys;
15+
use function gettype;
16+
use function is_array;
17+
use function is_object;
18+
use function is_scalar;
19+
use function json_encode;
20+
use function method_exists;
21+
use function str_contains;
22+
use function strtr;
1023

1124
/**
1225
* Convert Monolog LogRecord to Telemetry LogRecord with proper attribute mapping.
13-
*
14-
* This class handles the conversion of Monolog log records to Telemetry log records,
15-
* mapping Monolog's severity levels and applying appropriate prefixes to context
16-
* and extra attributes.
1726
*/
1827
final readonly class LogRecordConverter
1928
{
29+
private const string INTERPOLATION_DATE_FORMAT = 'Y-m-d\TH:i:s.uP';
30+
2031
public function __construct(
2132
private SeverityMapper $severityMapper = new SeverityMapper(),
2233
private ValueNormalizer $valueNormalizer = new ValueNormalizer(),
@@ -26,7 +37,7 @@ public function convert(LogRecord $record): TelemetryLogRecord
2637
{
2738
$telemetryRecord = new TelemetryLogRecord(
2839
severity: $this->severityMapper->map($record->level),
29-
body: $record->message,
40+
body: $this->interpolate($record->message, $record->context),
3041
);
3142

3243
return $this->applyAttributes($telemetryRecord, $record);
@@ -65,4 +76,65 @@ private function applyAttributes(TelemetryLogRecord $telemetryRecord, LogRecord
6576

6677
return $telemetryRecord;
6778
}
79+
80+
/**
81+
* @param array<array-key, mixed> $context
82+
*/
83+
private function interpolate(string $message, array $context): string
84+
{
85+
if (!str_contains($message, '{')) {
86+
return $message;
87+
}
88+
89+
$replacements = [];
90+
91+
foreach (array_keys($context) as $key) {
92+
$placeholder = '{' . $key . '}';
93+
94+
if (!str_contains($message, $placeholder)) {
95+
continue;
96+
}
97+
98+
$replacements[$placeholder] = $this->renderForInterpolation($context[$key]);
99+
}
100+
101+
return strtr($message, $replacements);
102+
}
103+
104+
private function renderForInterpolation(mixed $value): string
105+
{
106+
if ($value === null) {
107+
return '';
108+
}
109+
110+
if (is_scalar($value)) {
111+
return (string) $value;
112+
}
113+
114+
if (is_object($value) && method_exists($value, '__toString')) {
115+
return (string) $value;
116+
}
117+
118+
if ($value instanceof DateTimeInterface) {
119+
return $value->format(self::INTERPOLATION_DATE_FORMAT);
120+
}
121+
122+
if ($value instanceof BackedEnum) {
123+
return (string) $value->value;
124+
}
125+
126+
if ($value instanceof UnitEnum) {
127+
return $value->name;
128+
}
129+
130+
if (is_object($value)) {
131+
return '[object ' . $value::class . ']';
132+
}
133+
134+
if (is_array($value)) {
135+
return 'array' . (json_encode($value) ?: '');
136+
}
137+
138+
return '[' . gettype($value) . ']';
139+
}
68140
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\Bridge\Monolog\Telemetry\Tests\Fixtures;
6+
7+
enum InterpolationBackedEnumFixture: string
8+
{
9+
case Active = 'active';
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\Bridge\Monolog\Telemetry\Tests\Fixtures;
6+
7+
enum InterpolationUnitEnumFixture
8+
{
9+
case First;
10+
}

0 commit comments

Comments
 (0)