• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FriendsOfOpenTelemetry / opentelemetry-bundle / 7357653327

29 Dec 2023 02:59PM UTC coverage: 75.594% (-1.7%) from 77.27%
7357653327

push

github

gaelreyrol
feat(Doctrine): start autoinstrumentation

57 of 110 new or added lines in 6 files covered. (51.82%)

3 existing lines in 2 files now uncovered.

1208 of 1598 relevant lines covered (75.59%)

13.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.3
/src/DependencyInjection/OpenTelemetryExtension.php
1
<?php
2

3
namespace FriendsOfOpenTelemetry\OpenTelemetryBundle\DependencyInjection;
4

5
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Exporter\ExporterDsn;
6
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Exporter\ExporterOptionsInterface;
7
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Log\LogExporter\LogExporterEnum;
8
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Log\LoggerProvider\LoggerProviderEnum;
9
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Log\LoggerProvider\LoggerProviderFactoryInterface;
10
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Log\LogProcessor\LogProcessorEnum;
11
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Log\LogProcessor\LogProcessorFactoryInterface;
12
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Metric\MeterProvider\ExemplarFilterEnum;
13
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Metric\MeterProvider\MeterProviderEnum;
14
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Metric\MeterProvider\MeterProviderFactoryInterface;
15
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Metric\MetricExporter\MetricExporterEnum;
16
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Trace\SpanExporter\TraceExporterEnum;
17
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Trace\SpanProcessor\SpanProcessorEnum;
18
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Trace\SpanProcessor\SpanProcessorFactoryInterface;
19
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Trace\TracerProvider\TraceProviderEnum;
20
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Trace\TracerProvider\TracerProviderFactoryInterface;
21
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Trace\TracerProvider\TraceSamplerEnum;
22
use Monolog\Level;
23
use OpenTelemetry\Contrib\Logs\Monolog\Handler;
24
use OpenTelemetry\SDK\Logs\LoggerProviderInterface;
25
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
26
use OpenTelemetry\SDK\Metrics\MeterProviderInterface;
27
use OpenTelemetry\SDK\Trace\SpanProcessorInterface;
28
use OpenTelemetry\SDK\Trace\TracerProviderInterface;
29
use Symfony\Component\Config\FileLocator;
30
use Symfony\Component\Console\Application;
31
use Symfony\Component\DependencyInjection\ChildDefinition;
32
use Symfony\Component\DependencyInjection\ContainerBuilder;
33
use Symfony\Component\DependencyInjection\Definition;
34
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
35
use Symfony\Component\DependencyInjection\Reference;
36
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
37
use Symfony\Component\HttpKernel\HttpKernel;
38

39
/**
40
 * @phpstan-type ComponentInstrumentationOptions array{
41
 *     enabled: bool,
42
 *     tracer?: string,
43
 *     request_headers?: string[],
44
 *     response_headers?: string[],
45
 *     meter?: string,
46
 * }
47
 *
48
 * @phpstan-import-type ExporterOptions from ExporterOptionsInterface
49
 */
50
final class OpenTelemetryExtension extends ConfigurableExtension
51
{
52
    public const EXPORTER_OPTIONS = ['format', 'headers', 'compression', 'timeout', 'retry', 'max', 'ca', 'cert', 'key'];
53
    public const METRIC_EXPORTER_OPTIONS = ['temporality'];
54

55
    /** @phpstan-ignore-next-line */
56
    protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void
57
    {
58
        $loader = new PhpFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
8✔
59
        $loader->load('services.php');
8✔
60

61
        $this->loadService($mergedConfig['service'], $container);
8✔
62
        $this->loadTraces($mergedConfig['traces'], $container);
8✔
63
        $this->loadMetrics($mergedConfig['metrics'], $container);
8✔
64
        $this->loadLogs($mergedConfig['logs'], $container);
8✔
65
        $this->loadMonologHandlers($mergedConfig['logs'], $container);
8✔
66

67
        $this->loadHttpKernelInstrumentation($mergedConfig, $container);
8✔
68
        $this->loadConsoleInstrumentation($mergedConfig, $container);
8✔
69
    }
70

71
    /**
72
     * @param array{
73
     *     namespace: string,
74
     *     name: string,
75
     *     version: string,
76
     *     environment: string
77
     * } $config
78
     */
79
    private function loadService(array $config, ContainerBuilder $container): void
80
    {
81
        $container->setParameter('open_telemetry.service.namespace', $config['namespace']);
8✔
82
        $container->setParameter('open_telemetry.service.name', $config['name']);
8✔
83
        $container->setParameter('open_telemetry.service.version', $config['version']);
8✔
84
        $container->setParameter('open_telemetry.service.environment', $config['environment']);
8✔
85
    }
86

87
    /** @phpstan-ignore-next-line */
88
    private function loadHttpKernelInstrumentation(array $config, ContainerBuilder $container): void
89
    {
90
        $httpKernelConfig = $config['instrumentation']['http_kernel'];
8✔
91
        if (false === $httpKernelConfig['enabled']) {
8✔
92
            return;
8✔
93
        }
94

95
        if (!class_exists(HttpKernel::class)) {
×
96
            throw new \LogicException('To configure the HttpKernel instrumentation, you must first install the symfony/http-kernel package.');
×
97
        }
98

NEW
99
        $this->loadHttpKernelTracingInstrumentation($config, $container);
×
NEW
100
        $this->loadHttpKernelMeteringInstrumentation($config, $container);
×
101
    }
102

103
    /** @phpstan-ignore-next-line */
104
    public function loadHttpKernelTracingInstrumentation(array $config, ContainerBuilder $container): void
105
    {
NEW
106
        $httpKernelConfig = $config['instrumentation']['http_kernel'];
×
NEW
107
        $tracingHttpKernel = $httpKernelConfig['tracing'];
×
108

NEW
109
        if (false === $tracingHttpKernel['enabled']) {
×
NEW
110
            return;
×
111
        }
112

113
        $trace = $container
×
114
            ->getDefinition('open_telemetry.instrumentation.http_kernel.trace.event_subscriber')
×
NEW
115
            ->setArgument('$requestHeaders', $tracingHttpKernel['request_headers'])
×
NEW
116
            ->setArgument('$responseHeaders', $tracingHttpKernel['response_headers'])
×
UNCOV
117
            ->addTag('kernel.event_subscriber');
×
118

NEW
119
        if (isset($tracingHttpKernel['tracer'])) {
×
NEW
120
            $trace->setArgument('$tracer', new Reference(sprintf('open_telemetry.traces.tracers.%s', $tracingHttpKernel['tracer'])));
×
121
        } else {
122
            $defaultTracer = $config['traces']['default_tracer'] ?? array_key_first($config['traces']['tracers']);
×
123
            $trace->setArgument('$tracer', new Reference(sprintf('open_telemetry.traces.tracers.%s', $defaultTracer)));
×
124
        }
125
    }
126

127
    /** @phpstan-ignore-next-line */
128
    public function loadHttpKernelMeteringInstrumentation(array $config, ContainerBuilder $container): void
129
    {
NEW
130
        $httpKernelConfig = $config['instrumentation']['http_kernel'];
×
NEW
131
        $meteringHttpKernel = $httpKernelConfig['metering'];
×
132

NEW
133
        if (false === $meteringHttpKernel['enabled']) {
×
NEW
134
            return;
×
135
        }
136

NEW
137
        $metric = $container
×
NEW
138
            ->getDefinition('open_telemetry.instrumentation.http_kernel.metric.event_subscriber')
×
NEW
139
            ->addTag('kernel.event_subscriber');
×
140

NEW
141
        if (isset($meteringHttpKernel['meter'])) {
×
NEW
142
            $metric->setArgument('$meter', new Reference(sprintf('open_telemetry.metrics.meters.%s', $meteringHttpKernel['meter'])));
×
NEW
143
            if (!isset($config['metrics']['meters'][$meteringHttpKernel['meter']]['provider'])) {
×
UNCOV
144
                throw new \InvalidArgumentException('Meter provider has not found');
×
145
            }
NEW
146
            $meterProvider = $config['metrics']['meters'][$meteringHttpKernel['meter']]['provider'];
×
147
            $metric->setArgument('$meterProvider', new Reference(sprintf('open_telemetry.metrics.providers.%s', $meterProvider)));
×
148
        } else {
149
            $defaultMeter = $config['metrics']['default_meter'] ?? array_key_first($config['metrics']['meters']);
×
150
            $metric->setArgument('$meter', new Reference(sprintf('open_telemetry.metrics.meters.%s', $defaultMeter)));
×
151
            if (!isset($config['metrics']['meters'][$defaultMeter]['provider'])) {
×
152
                throw new \InvalidArgumentException('Meter provider has not found');
×
153
            }
154
            $meterProvider = $config['metrics']['meters'][$defaultMeter]['provider'];
×
155
            $metric->setArgument('$meterProvider', new Reference(sprintf('open_telemetry.metrics.providers.%s', $meterProvider)));
×
156
        }
157
    }
158

159
    /** @phpstan-ignore-next-line */
160
    private function loadConsoleInstrumentation(array $config, ContainerBuilder $container): void
161
    {
162
        $consoleConfig = $config['instrumentation']['console'];
8✔
163
        if (false === $consoleConfig['enabled']) {
8✔
164
            return;
8✔
165
        }
166

167
        if (!class_exists(Application::class)) {
×
168
            throw new \LogicException('To configure the Console instrumentation, you must first install the symfony/console package.');
×
169
        }
170

NEW
171
        $this->loadConsoleTracingInstrumentation($config, $container);
×
NEW
172
        $this->loadConsoleMeteringInstrumentation($config, $container);
×
173
    }
174

175
    /** @phpstan-ignore-next-line */
176
    public function loadConsoleTracingInstrumentation(array $config, ContainerBuilder $container): void
177
    {
NEW
178
        $consoleConfig = $config['instrumentation']['console'];
×
NEW
179
        $tracingConsoleConfig = $consoleConfig['tracing'];
×
NEW
180
        if (false === $consoleConfig['enabled']) {
×
NEW
181
            return;
×
182
        }
183

NEW
184
        $trace = $container
×
NEW
185
            ->getDefinition('open_telemetry.instrumentation.console.trace.event_subscriber')
×
NEW
186
            ->addTag('kernel.event_subscriber');
×
187

NEW
188
        if (isset($tracingConsoleConfig['tracer'])) {
×
NEW
189
            $trace->setArgument('$tracer', new Reference(sprintf('open_telemetry.traces.tracers.%s', $tracingConsoleConfig['tracer'])));
×
190
        } else {
191
            $defaultTracer = $config['traces']['default_tracer'] ?? array_key_first($config['traces']['tracers']);
×
192
            $trace->setArgument('$tracer', new Reference(sprintf('open_telemetry.traces.tracers.%s', $defaultTracer)));
×
193
        }
194
    }
195

196
    /** @phpstan-ignore-next-line */
197
    public function loadConsoleMeteringInstrumentation(array $config, ContainerBuilder $container): void
198
    {
NEW
199
        $consoleConfig = $config['instrumentation']['console'];
×
NEW
200
        $meteringConsoleConfig = $consoleConfig['metering'];
×
NEW
201
        if (false === $consoleConfig['enabled']) {
×
NEW
202
            return;
×
203
        }
204

NEW
205
        $metric = $container
×
NEW
206
            ->getDefinition('open_telemetry.instrumentation.console.metric.event_subscriber')
×
NEW
207
            ->addTag('kernel.event_subscriber');
×
208

NEW
209
        if (isset($meteringConsoleConfig['meter'])) {
×
210
            $metric->setArgument('$meter', new Reference(sprintf('open_telemetry.metrics.meters.%s', $config['meter'])));
×
NEW
211
            if (!isset($config['metrics']['meters'][$meteringConsoleConfig['meter']]['provider'])) {
×
212
                throw new \InvalidArgumentException('Meter provider has not found');
×
213
            }
NEW
214
            $meterProvider = $config['metrics']['meters'][$meteringConsoleConfig['meter']]['provider'];
×
215
            $metric->setArgument('$meterProvider', new Reference(sprintf('open_telemetry.metrics.providers.%s', $meterProvider)));
×
216
        } else {
217
            $defaultMeter = $config['metrics']['default_meter'] ?? array_key_first($config['metrics']['meters']);
×
218
            $metric->setArgument('$meter', new Reference(sprintf('open_telemetry.metrics.meters.%s', $defaultMeter)));
×
219
            if (!isset($config['metrics']['meters'][$defaultMeter]['provider'])) {
×
220
                throw new \InvalidArgumentException('Meter provider has not found');
×
221
            }
222
            $meterProvider = $config['metrics']['meters'][$defaultMeter]['provider'];
×
223
            $metric->setArgument('$meterProvider', new Reference(sprintf('open_telemetry.metrics.providers.%s', $meterProvider)));
×
224
        }
225
    }
226

227
    /**
228
     * @param array{
229
     *     default_tracer?: string,
230
     *     tracers: array<string, mixed>,
231
     *     exporters: array<string, mixed>,
232
     *     processors: array<string, mixed>,
233
     *     providers: array<string, mixed>
234
     * } $config
235
     */
236
    private function loadTraces(array $config, ContainerBuilder $container): void
237
    {
238
        foreach ($config['exporters'] as $name => $exporter) {
8✔
239
            $this->loadTraceExporter($name, $exporter, $container);
2✔
240
        }
241

242
        foreach ($config['processors'] as $name => $processor) {
8✔
243
            $this->loadTraceProcessor($name, $processor, $container);
2✔
244
        }
245

246
        foreach ($config['providers'] as $name => $provider) {
8✔
247
            $this->loadTraceProvider($name, $provider, $container);
4✔
248
        }
249

250
        foreach ($config['tracers'] as $name => $tracer) {
8✔
251
            $this->loadTraceTracer($name, $tracer, $container);
8✔
252
        }
253

254
        $defaultTracer = $config['default_tracer'] ?? null;
8✔
255
        if (0 < count($config['tracers'])) {
8✔
256
            $defaultTracer = array_key_first($config['tracers']);
8✔
257
        }
258

259
        if (null !== $defaultTracer) {
8✔
260
            $container->set('open_telemetry.traces.default_tracer', new Reference(sprintf('open_telemetry.traces.tracers.%s', $defaultTracer)));
8✔
261
        }
262
    }
263

264
    /**
265
     * @param array{
266
     *      dsn: string,
267
     *      options?: ExporterOptions
268
     *  } $options
269
     */
270
    private function loadTraceExporter(string $name, array $options, ContainerBuilder $container): void
271
    {
272
        $exporterId = sprintf('open_telemetry.traces.exporters.%s', $name);
2✔
273
        $dsn = ExporterDsn::fromString($options['dsn']);
2✔
274
        $exporter = TraceExporterEnum::from($dsn->getExporter());
2✔
275

276
        $container
2✔
277
            ->setDefinition($exporterId, new ChildDefinition('open_telemetry.traces.exporter'))
2✔
278
            ->setClass($exporter->getClass())
2✔
279
            ->setFactory([$exporter->getFactoryClass(), 'createExporter'])
2✔
280
            ->setArguments([
2✔
281
                '$dsn' => $this->createExporterDsnDefinition($options['dsn'], $container),
2✔
282
                '$options' => $this->createExporterOptionsDefinition($options['options'] ?? [], $container),
2✔
283
            ]);
2✔
284
    }
285

286
    /**
287
     * @param array{
288
     *      type: string,
289
     *      processors?: string[],
290
     *      exporter?: string
291
     *  } $processor
292
     */
293
    private function loadTraceProcessor(string $name, array $processor, ContainerBuilder $container): void
294
    {
295
        $processorId = sprintf('open_telemetry.traces.processors.%s', $name);
2✔
296
        $options = $this->getTraceProcessorOptions($processor);
2✔
297

298
        $container
2✔
299
            ->setDefinition($processorId, new ChildDefinition('open_telemetry.traces.processor'))
2✔
300
            ->setClass($options['class'])
2✔
301
            ->setFactory([$options['factory'], 'createProcessor'])
2✔
302
            ->setArguments([
2✔
303
                '$processors' => $options['processors'],
2✔
304
                '$exporter' => $options['exporter'],
2✔
305
            ]);
2✔
306
    }
307

308
    /**
309
     * @param array{
310
     *     type: string,
311
     *     processors?: string[],
312
     *     exporter?: string
313
     * } $processor
314
     *
315
     * @return array{
316
     *     factory: class-string<SpanProcessorFactoryInterface>,
317
     *     class: class-string<SpanProcessorInterface>,
318
     *     processors: ?Reference[],
319
     *     exporter: ?Reference,
320
     * }
321
     */
322
    private function getTraceProcessorOptions(array $processor): array
323
    {
324
        $processorEnum = SpanProcessorEnum::from($processor['type']);
2✔
325
        $options = [
2✔
326
            'factory' => $processorEnum->getFactoryClass(),
2✔
327
            'class' => $processorEnum->getClass(),
2✔
328
            'processors' => [],
2✔
329
            'exporter' => null,
2✔
330
        ];
2✔
331

332
        // if (SpanProcessorEnum::Batch === $options['type']) {
333
        //     // TODO: Check batch options
334
        //     clock: OpenTelemetry\SDK\Common\Time\SystemClock
335
        //     max_queue_size: 2048
336
        //     schedule_delay: 5000
337
        //     export_timeout: 30000
338
        //     max_export_batch_size: 512
339
        //     auto_flush: true
340
        // }
341

342
        if (SpanProcessorEnum::Multi === $processorEnum) {
2✔
343
            if (!isset($processor['processors']) || 0 === count($processor['processors'])) {
×
344
                throw new \InvalidArgumentException('Processors are not set or empty');
×
345
            }
346
            $options['processors'] = array_map(
×
347
                fn (string $processor) => new Reference(sprintf('open_telemetry.traces.processors.%s', $processor)),
×
348
                $processor['processors'],
×
349
            );
×
350
        }
351

352
        if (SpanProcessorEnum::Simple === $processorEnum) {
2✔
353
            if (!isset($processor['exporter'])) {
2✔
354
                throw new \InvalidArgumentException('Exporter is not set');
×
355
            }
356
            $options['exporter'] = new Reference(sprintf('open_telemetry.traces.exporters.%s', $processor['exporter']));
2✔
357
        }
358

359
        return $options;
2✔
360
    }
361

362
    /**
363
     * @param array{
364
     *     type: string,
365
     *     sampler?: array{type: string, ratio?: float, parent?: string},
366
     *     processors?: string[]
367
     * } $provider
368
     */
369
    private function loadTraceProvider(string $name, array $provider, ContainerBuilder $container): void
370
    {
371
        $providerId = sprintf('open_telemetry.traces.providers.%s', $name);
4✔
372
        $options = $this->getTraceProviderOptions($provider);
4✔
373

374
        $sampler = isset($provider['sampler']) ? $this->getTraceSamplerDefinition($provider['sampler'], $container) : $container->getDefinition('open_telemetry.traces.samplers.always_on');
4✔
375

376
        $container
4✔
377
            ->setDefinition($providerId, new ChildDefinition('open_telemetry.traces.provider'))
4✔
378
            ->setClass($options['class'])
4✔
379
            ->setFactory([$options['factory'], 'createProvider'])
4✔
380
            ->setArguments([
4✔
381
                '$sampler' => $sampler,
4✔
382
                '$processors' => $options['processors'],
4✔
383
            ]);
4✔
384
    }
385

386
    /**
387
     * @param array{type: string, ratio?: float, parent?: string} $sampler
388
     */
389
    private function getTraceSamplerDefinition(array $sampler, ContainerBuilder $container): Definition
390
    {
391
        $type = TraceSamplerEnum::from($sampler['type']);
2✔
392

393
        if (TraceSamplerEnum::TraceIdRatio === $type && !isset($sampler['ratio'])) {
2✔
394
            throw new \InvalidArgumentException(sprintf("Sampler of type '%s' requires a ratio parameter.", $type->value));
×
395
        }
396

397
        if (TraceSamplerEnum::ParentBased === $type) {
2✔
398
            if (!isset($sampler['parent'])) {
×
399
                throw new \InvalidArgumentException(sprintf("Sampler of type '%s' requires a parent parameter.", $type->value));
×
400
            }
401
            $parentSampler = TraceSamplerEnum::tryFrom($sampler['parent']);
×
402
            if (!in_array($parentSampler, [TraceSamplerEnum::AlwaysOn, TraceSamplerEnum::AlwaysOff], true)) {
×
403
                throw new \InvalidArgumentException(sprintf("Unsupported '%s' parent sampler", $parentSampler->value));
×
404
            }
405
        }
406

407
        return match ($type) {
2✔
408
            TraceSamplerEnum::AlwaysOn => $container->getDefinition('open_telemetry.traces.samplers.always_on'),
2✔
409
            TraceSamplerEnum::AlwaysOff => $container->getDefinition('open_telemetry.traces.samplers.always_off'),
2✔
410
            TraceSamplerEnum::TraceIdRatio => $container
2✔
411
                ->getDefinition('open_telemetry.traces.samplers.trace_id_ratio_based')
2✔
412
                ->setArgument('$probability', $sampler['ratio']),
2✔
413
            TraceSamplerEnum::ParentBased => $container
2✔
414
                ->getDefinition('open_telemetry.traces.samplers.parent_based')
2✔
415
                ->setArgument('$root', $this->getTraceSamplerDefinition([
2✔
416
                    'type' => $sampler['parent'],
2✔
417
                ], $container)),
2✔
418
        };
2✔
419
    }
420

421
    /**
422
     * @param array{
423
     *     type: string,
424
     *     processors?: string[]
425
     * } $provider
426
     *
427
     * @return array{
428
     *     factory: class-string<TracerProviderFactoryInterface>,
429
     *     class: class-string<TracerProviderInterface>,
430
     *     processors: ?Reference[],
431
     * }
432
     */
433
    private function getTraceProviderOptions(array $provider): array
434
    {
435
        $providerEnum = TraceProviderEnum::from($provider['type']);
4✔
436
        $options = [
4✔
437
            'factory' => $providerEnum->getFactoryClass(),
4✔
438
            'class' => $providerEnum->getClass(),
4✔
439
            'processors' => [],
4✔
440
        ];
4✔
441

442
        if (TraceProviderEnum::Default === $providerEnum) {
4✔
443
            if (!isset($provider['processors']) || 0 === count($provider['processors'])) {
2✔
444
                throw new \InvalidArgumentException('Processors are not set or empty');
×
445
            }
446
            $options['processors'] = array_map(
2✔
447
                fn (string $processor) => new Reference(sprintf('open_telemetry.traces.processors.%s', $processor)),
2✔
448
                $provider['processors']
2✔
449
            );
2✔
450
        }
451

452
        return $options;
4✔
453
    }
454

455
    /**
456
     * @param array{
457
     *     name?: string,
458
     *     version?: string,
459
     *     provider: string
460
     * } $tracer
461
     */
462
    private function loadTraceTracer(string $name, array $tracer, ContainerBuilder $container): void
463
    {
464
        $tracerId = sprintf('open_telemetry.traces.tracers.%s', $name);
8✔
465

466
        $container
8✔
467
            ->setDefinition($tracerId, new ChildDefinition('open_telemetry.traces.tracer'))
8✔
468
            ->setPublic(true)
8✔
469
            ->setFactory([
8✔
470
                new Reference(sprintf('open_telemetry.traces.providers.%s', $tracer['provider'])),
8✔
471
                'getTracer',
8✔
472
            ])
8✔
473
            ->setArguments([
8✔
474
                $tracer['name'] ?? $container->getParameter('open_telemetry.bundle.name'),
8✔
475
                $tracer['version'] ?? $container->getParameter('open_telemetry.bundle.version'),
8✔
476
            ]);
8✔
477
    }
478

479
    /**
480
     * @param array{
481
     *     default_meter?: string,
482
     *     meters: array<string, mixed>,
483
     *     exporters: array<string, mixed>,
484
     *     providers: array<string, mixed>
485
     * } $config
486
     */
487
    private function loadMetrics(array $config, ContainerBuilder $container): void
488
    {
489
        foreach ($config['exporters'] as $name => $exporter) {
8✔
490
            $this->loadMetricExporter($name, $exporter, $container);
2✔
491
        }
492

493
        foreach ($config['providers'] as $name => $provider) {
8✔
494
            $this->loadMetricProvider($name, $provider, $container);
4✔
495
        }
496

497
        foreach ($config['meters'] as $name => $meter) {
8✔
498
            $this->loadMetricMeter($name, $meter, $container);
8✔
499
        }
500

501
        $defaultMeter = $config['default_meter'] ?? null;
8✔
502
        if (0 < count($config['meters'])) {
8✔
503
            $defaultMeter = array_key_first($config['meters']);
8✔
504
        }
505

506
        if (null !== $defaultMeter) {
8✔
507
            $container->set('open_telemetry.metrics.default_meter', new Reference(sprintf('open_telemetry.metrics.meters.%s', $defaultMeter)));
8✔
508
        }
509
    }
510

511
    /**
512
     * @param array{
513
     *     dsn: string,
514
     *     options?: ExporterOptions
515
     * } $options
516
     */
517
    private function loadMetricExporter(string $name, array $options, ContainerBuilder $container): void
518
    {
519
        $exporterId = sprintf('open_telemetry.metrics.exporters.%s', $name);
2✔
520
        $dsn = ExporterDsn::fromString($options['dsn']);
2✔
521
        $exporter = MetricExporterEnum::from($dsn->getExporter());
2✔
522

523
        $container
2✔
524
            ->setDefinition($exporterId, new ChildDefinition('open_telemetry.metrics.exporter'))
2✔
525
            ->setClass($exporter->getClass())
2✔
526
            ->setFactory([$exporter->getFactoryClass(), 'createExporter'])
2✔
527
            ->setArguments([
2✔
528
                '$dsn' => $this->createExporterDsnDefinition($options['dsn'], $container),
2✔
529
                '$options' => $this->createExporterOptionsDefinition(
2✔
530
                    $options['options'] ?? [],
2✔
531
                    $container,
2✔
532
                    'open_telemetry.metric_exporter_options',
2✔
533
                    self::METRIC_EXPORTER_OPTIONS
2✔
534
                ),
2✔
535
            ]);
2✔
536
    }
537

538
    /**
539
     * @param array{
540
     *     type: string,
541
     *     exporter?: string,
542
     *     filter?: string
543
     * } $provider
544
     */
545
    private function loadMetricProvider(string $name, array $provider, ContainerBuilder $container): void
546
    {
547
        $providerId = sprintf('open_telemetry.metrics.providers.%s', $name);
4✔
548
        $options = $this->getMetricProviderOptions($provider);
4✔
549

550
        $container
4✔
551
            ->setDefinition($providerId, new ChildDefinition('open_telemetry.metrics.provider'))
4✔
552
            ->setClass($options['class'])
4✔
553
            ->setFactory([$options['factory'], 'createProvider'])
4✔
554
            ->setArguments([
4✔
555
                '$exporter' => $options['exporter'],
4✔
556
                '$filter' => $options['filter'],
4✔
557
            ]);
4✔
558
    }
559

560
    /**
561
     * @param array{
562
     *     type: string,
563
     *     exporter?: string,
564
     *     filter?: string,
565
     * } $provider
566
     *
567
     * @return array{
568
     *     factory: class-string<MeterProviderFactoryInterface>,
569
     *     class: class-string<MeterProviderInterface>,
570
     *     exporter: ?Reference,
571
     *     filter: ?Reference,
572
     * }
573
     */
574
    private function getMetricProviderOptions(array $provider): array
575
    {
576
        $providerEnum = MeterProviderEnum::from($provider['type']);
4✔
577
        $options = [
4✔
578
            'factory' => $providerEnum->getFactoryClass(),
4✔
579
            'class' => $providerEnum->getClass(),
4✔
580
            'exporter' => null,
4✔
581
            'filter' => null,
4✔
582
        ];
4✔
583

584
        if (MeterProviderEnum::Default === $providerEnum) {
4✔
585
            if (!isset($provider['exporter'])) {
2✔
586
                throw new \InvalidArgumentException('Exporter is not set');
×
587
            }
588
            $options['exporter'] = new Reference(sprintf('open_telemetry.metrics.exporters.%s', $provider['exporter']));
2✔
589
        }
590

591
        $filter = isset($provider['filter']) ? ExemplarFilterEnum::from($provider['filter']) : ExemplarFilterEnum::All;
4✔
592
        $options['filter'] = match ($filter) {
4✔
593
            ExemplarFilterEnum::WithSampledTrace => new Reference('open_telemetry.metrics.exemplar_filters.with_sampled_trace'),
4✔
594
            ExemplarFilterEnum::All => new Reference('open_telemetry.metrics.exemplar_filters.all'),
4✔
595
            ExemplarFilterEnum::None => new Reference('open_telemetry.metrics.exemplar_filters.none'),
4✔
596
        };
4✔
597

598
        return $options;
4✔
599
    }
600

601
    /**
602
     * @param array{
603
     *     provider: string,
604
     *     name?: string,
605
     *     version?: string,
606
     * } $meter
607
     */
608
    private function loadMetricMeter(string $name, array $meter, ContainerBuilder $container): void
609
    {
610
        $meterId = sprintf('open_telemetry.metrics.meters.%s', $name);
8✔
611

612
        $container
8✔
613
            ->setDefinition($meterId, new ChildDefinition('open_telemetry.metrics.meter'))
8✔
614
            ->setPublic(true)
8✔
615
            ->setFactory([
8✔
616
                new Reference(sprintf('open_telemetry.metrics.providers.%s', $meter['provider'])),
8✔
617
                'getMeter',
8✔
618
            ])
8✔
619
            ->setArguments([
8✔
620
                $meter['name'] ?? $container->getParameter('open_telemetry.bundle.name'),
8✔
621
                $meter['version'] ?? $container->getParameter('open_telemetry.bundle.version'),
8✔
622
            ]);
8✔
623
    }
624

625
    /**
626
     * @param array{
627
     *     default_logger?: string,
628
     *     loggers: array<string, mixed>,
629
     *     exporters: array<string, mixed>,
630
     *     processors: array<string, mixed>,
631
     *     providers: array<string, mixed>
632
     * } $config
633
     */
634
    private function loadLogs(array $config, ContainerBuilder $container): void
635
    {
636
        foreach ($config['exporters'] as $name => $exporter) {
8✔
637
            $this->loadLogExporter($name, $exporter, $container);
2✔
638
        }
639

640
        foreach ($config['processors'] as $name => $processor) {
8✔
641
            $this->loadLogProcessor($name, $processor, $container);
2✔
642
        }
643

644
        foreach ($config['providers'] as $name => $provider) {
8✔
645
            $this->loadLogProvider($name, $provider, $container);
4✔
646
        }
647

648
        foreach ($config['loggers'] as $name => $logger) {
8✔
649
            $this->loadLogLogger($name, $logger, $container);
8✔
650
        }
651

652
        $defaultLogger = $config['default_logger'] ?? null;
8✔
653
        if (0 < count($config['loggers'])) {
8✔
654
            $defaultLogger = array_key_first($config['loggers']);
8✔
655
        }
656

657
        if (null !== $defaultLogger) {
8✔
658
            $container->set('open_telemetry.logs.default_logger', new Reference(sprintf('open_telemetry.logs.loggers.%s', $defaultLogger)));
8✔
659
        }
660
    }
661

662
    /**
663
     * @param array{
664
     *      dsn: string,
665
     *      options?: ExporterOptions
666
     *  } $options
667
     */
668
    private function loadLogExporter(string $name, array $options, ContainerBuilder $container): void
669
    {
670
        $exporterId = sprintf('open_telemetry.logs.exporters.%s', $name);
2✔
671
        $dsn = ExporterDsn::fromString($options['dsn']);
2✔
672
        $exporter = LogExporterEnum::from($dsn->getExporter());
2✔
673

674
        $container
2✔
675
            ->setDefinition($exporterId, new ChildDefinition('open_telemetry.logs.exporter'))
2✔
676
            ->setClass($exporter->getClass())
2✔
677
            ->setFactory([$exporter->getFactoryClass(), 'createExporter'])
2✔
678
            ->setArguments([
2✔
679
                '$dsn' => $this->createExporterDsnDefinition($options['dsn'], $container),
2✔
680
                '$options' => $this->createExporterOptionsDefinition($options['options'] ?? [], $container),
2✔
681
            ]);
2✔
682
    }
683

684
    /**
685
     * @param array{
686
     *      type: string,
687
     *      processors?: string[],
688
     *      exporter?: string
689
     *  } $processor
690
     */
691
    private function loadLogProcessor(string $name, array $processor, ContainerBuilder $container): void
692
    {
693
        $processorId = sprintf('open_telemetry.logs.processors.%s', $name);
2✔
694
        $options = $this->getLogProcessorOptions($processor);
2✔
695

696
        $container
2✔
697
            ->setDefinition($processorId, new ChildDefinition('open_telemetry.logs.processor'))
2✔
698
            ->setClass($options['class'])
2✔
699
            ->setFactory([$options['factory'], 'createProcessor'])
2✔
700
            ->setArguments([
2✔
701
                '$processors' => $options['processors'],
2✔
702
                '$exporter' => $options['exporter'],
2✔
703
            ]);
2✔
704
    }
705

706
    /**
707
     * @param array{
708
     *     type: string,
709
     *     processors?: string[],
710
     *     exporter?: string
711
     * } $processor
712
     *
713
     * @return array{
714
     *     factory: class-string<LogProcessorFactoryInterface>,
715
     *     class: class-string<LogRecordProcessorInterface>,
716
     *     processors: ?Reference[],
717
     *     exporter: ?Reference,
718
     * }
719
     */
720
    private function getLogProcessorOptions(array $processor): array
721
    {
722
        $processorEnum = LogProcessorEnum::from($processor['type']);
2✔
723
        $options = [
2✔
724
            'factory' => $processorEnum->getFactoryClass(),
2✔
725
            'class' => $processorEnum->getClass(),
2✔
726
            'processors' => [],
2✔
727
            'exporter' => null,
2✔
728
        ];
2✔
729

730
        // if (LogProcessorEnum::Batch === $processorEnum) {
731
        //     // TODO: Check batch options
732
        //     clock: OpenTelemetry\SDK\Common\Time\SystemClock
733
        //     max_queue_size: 2048
734
        //     schedule_delay: 5000
735
        //     export_timeout: 30000
736
        //     max_export_batch_size: 512
737
        //     auto_flush: true
738
        // }
739

740
        if (LogProcessorEnum::Multi === $processorEnum) {
2✔
741
            if (!isset($processor['processors']) || 0 === count($processor['processors'])) {
×
742
                throw new \InvalidArgumentException('Processors are not set or empty');
×
743
            }
744
            $options['processors'] = array_map(
×
745
                fn (string $processor) => new Reference(sprintf('open_telemetry.logs.processors.%s', $processor)),
×
746
                $processor['processors'],
×
747
            );
×
748
        }
749

750
        if (LogProcessorEnum::Simple === $processorEnum) {
2✔
751
            if (!isset($processor['exporter'])) {
2✔
752
                throw new \InvalidArgumentException('Exporter is not set');
×
753
            }
754
            $options['exporter'] = new Reference(sprintf('open_telemetry.logs.exporters.%s', $processor['exporter']));
2✔
755
        }
756

757
        return $options;
2✔
758
    }
759

760
    /**
761
     * @param array{
762
     *     type: string,
763
     *     processor?: string,
764
     * } $provider
765
     */
766
    private function loadLogProvider(string $name, array $provider, ContainerBuilder $container): void
767
    {
768
        $providerId = sprintf('open_telemetry.logs.providers.%s', $name);
4✔
769
        $options = $this->getLoggerProviderOptions($provider);
4✔
770

771
        $container
4✔
772
            ->setDefinition($providerId, new ChildDefinition('open_telemetry.logs.provider'))
4✔
773
            ->setClass($options['class'])
4✔
774
            ->setFactory([$options['factory'], 'createProvider'])
4✔
775
            ->setArguments([
4✔
776
                '$processor' => $options['processor'],
4✔
777
            ]);
4✔
778
    }
779

780
    /**
781
     * @param array{
782
     *     type: string,
783
     *     processor?: string,
784
     * } $provider
785
     *
786
     * @return array{
787
     *     factory: class-string<LoggerProviderFactoryInterface>,
788
     *     class: class-string<LoggerProviderInterface>,
789
     *     processor: ?Reference,
790
     * }
791
     */
792
    private function getLoggerProviderOptions(array $provider): array
793
    {
794
        $providerEnum = LoggerProviderEnum::from($provider['type']);
4✔
795
        $options = [
4✔
796
            'factory' => $providerEnum->getFactoryClass(),
4✔
797
            'class' => $providerEnum->getClass(),
4✔
798
            'processor' => null,
4✔
799
        ];
4✔
800

801
        if (LoggerProviderEnum::Default === $providerEnum) {
4✔
802
            if (!isset($provider['processor'])) {
2✔
803
                throw new \InvalidArgumentException('Processor is not set');
×
804
            }
805
            $options['processor'] = new Reference(sprintf('open_telemetry.logs.processors.%s', $provider['processor']));
2✔
806
        }
807

808
        return $options;
4✔
809
    }
810

811
    /**
812
     * @param array{
813
     *     provider: string,
814
     *     name?: string,
815
     *     version?: string,
816
     * } $logger
817
     */
818
    private function loadLogLogger(string $name, array $logger, ContainerBuilder $container): void
819
    {
820
        $loggerId = sprintf('open_telemetry.logs.loggers.%s', $name);
8✔
821

822
        $container
8✔
823
            ->setDefinition($loggerId, new ChildDefinition('open_telemetry.logs.logger'))
8✔
824
            ->setPublic(true)
8✔
825
            ->setFactory([
8✔
826
                new Reference(sprintf('open_telemetry.logs.providers.%s', $logger['provider'])),
8✔
827
                'getLogger',
8✔
828
            ])
8✔
829
            ->setArguments([
8✔
830
                $logger['name'] ?? $container->getParameter('open_telemetry.bundle.name'),
8✔
831
                $logger['version'] ?? $container->getParameter('open_telemetry.bundle.version'),
8✔
832
            ]);
8✔
833
    }
834

835
    /**
836
     * @param array{
837
     *     default_logger?: string,
838
     *     monolog: array{enabled: bool, handlers: array<array{handler: string, provider: string, level: string, bubble: bool}>},
839
     *     loggers: array<string, mixed>,
840
     *     exporters: array<string, mixed>,
841
     *     processors: array<string, mixed>,
842
     *     providers: array<string, mixed>
843
     * } $config
844
     */
845
    private function loadMonologHandlers(array $config, ContainerBuilder $container): void
846
    {
847
        if (false === $config['monolog']['enabled']) {
8✔
848
            return;
8✔
849
        }
850

851
        if (!class_exists(Handler::class)) {
×
852
            throw new \LogicException('To configure the Monolog handler, you must first install the open-telemetry/opentelemetry-logger-monolog package.');
×
853
        }
854

855
        foreach ($config['monolog']['handlers'] as $name => $handler) {
×
856
            $handlerId = sprintf('open_telemetry.logs.monolog.handlers.%s', $name);
×
857
            $container
×
858
                ->setDefinition($handlerId, new ChildDefinition('open_telemetry.logs.monolog.handler'))
×
859
                ->setPublic(true)
×
860
                ->setArguments([
×
861
                    '$loggerProvider' => new Reference(sprintf('open_telemetry.logs.providers.%s', $handler['provider'])),
×
862
                    '$level' => Level::fromName(ucfirst($handler['level'])),
×
863
                    '$bubble' => $handler['bubble'],
×
864
                ]);
×
865
        }
866
    }
867

868
    private function createExporterDsnDefinition(string $dsn, ContainerBuilder $container): Definition
869
    {
870
        return $container
6✔
871
            ->getDefinition('open_telemetry.exporter_dsn')
6✔
872
            ->setArguments([$dsn]);
6✔
873
    }
874

875
    /**
876
     * @param array<string, mixed> $configuration
877
     * @param string[]             $extraOptions
878
     */
879
    private function createExporterOptionsDefinition(
880
        array $configuration,
881
        ContainerBuilder $container,
882
        string $definition = 'open_telemetry.exporter_options',
883
        array $extraOptions = [],
884
    ): Definition {
885
        return $container
6✔
886
            ->getDefinition($definition)
6✔
887
            ->setArguments([array_filter(
6✔
888
                $configuration,
6✔
889
                fn (string $key) => in_array(
6✔
890
                    $key,
6✔
891
                    self::EXPORTER_OPTIONS + $extraOptions,
6✔
892
                    true,
6✔
893
                ), ARRAY_FILTER_USE_KEY),
6✔
894
            ]);
6✔
895
    }
896
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc