From 223de25cf450e5f0eb4a214b340e1166c14aa467 Mon Sep 17 00:00:00 2001 From: Brett McBride Date: Thu, 27 Jun 2024 16:53:45 +1000 Subject: [PATCH 1/4] adding export retry configuration --- .../Logs/LogRecordExporterOtlp.php | 17 +++++++++ .../Metrics/MetricExporterOtlp.php | 17 +++++++++ .../Trace/SpanExporterOtlp.php | 17 +++++++++ .../Trace/SpanExporterZipkin.php | 17 +++++++++ .../Integration/Config/ConfigurationTest.php | 1 + .../Config/configurations/anchors.yaml | 3 ++ .../Config/configurations/kitchen-sink.yaml | 12 +++++++ .../Config/configurations/minimal.yaml | 35 +++++++++++++++++++ .../ExampleSdk/Trace/SpanExporterOtlp.php | 8 +++++ .../configurations/kitchen-sink.yaml | 3 ++ 10 files changed, 130 insertions(+) create mode 100644 tests/Integration/Config/configurations/minimal.yaml diff --git a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php index 6ee171296..d70719e18 100644 --- a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php @@ -34,6 +34,10 @@ final class LogRecordExporterOtlp implements ComponentProvider * headers: array, * compression: 'gzip'|null, * timeout: int<0, max>, + * retry: array{ + * initial_delay: int, + * max_attempts: int + * } * } $properties */ public function createPlugin(array $properties, Context $context): LogRecordExporterInterface @@ -46,6 +50,8 @@ public function createPlugin(array $properties, Context $context): LogRecordExpo headers: $properties['headers'], compression: $properties['compression'], timeout: $properties['timeout'], + retryDelay: $properties['retry']['initial_delay'], + maxRetries: $properties['retry']['max_attempts'], cacert: $properties['certificate'], cert: $properties['client_certificate'], key: $properties['client_certificate'], @@ -67,7 +73,18 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->end() ->enumNode('compression')->values(['gzip'])->defaultNull()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() + ->arrayNode('retry') + ->children() + ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() + ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() + ->end() + ->end() ->end() + ->beforeNormalization()->ifTrue(function ($data): bool { + return !array_key_exists('retry', $data); + })->then(function ($data): array { + return $data + ['retry' => []]; + })->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php index b94015b26..b1f344e98 100644 --- a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php @@ -35,6 +35,10 @@ final class MetricExporterOtlp implements ComponentProvider * headers: array, * compression: 'gzip'|null, * timeout: int<0, max>, + * retry: array{ + * initial_delay: int, + * max_attempts: int, + * }, * temporality_preference: 'cumulative'|'delta'|'lowmemory', * default_histogram_aggregation: 'explicit_bucket_histogram', * } $properties @@ -55,6 +59,8 @@ public function createPlugin(array $properties, Context $context): MetricExporte headers: $properties['headers'], compression: $properties['compression'], timeout: $properties['timeout'], + retryDelay: $properties['retry']['initial_delay'], + maxRetries: $properties['retry']['max_attempts'], cacert: $properties['certificate'], cert: $properties['client_certificate'], key: $properties['client_certificate'], @@ -76,6 +82,12 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->end() ->enumNode('compression')->values(['gzip'])->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() + ->arrayNode('retry') + ->children() + ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() + ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() + ->end() + ->end() ->enumNode('temporality_preference') ->values(['cumulative', 'delta', 'lowmemory']) ->defaultValue('cumulative') @@ -85,6 +97,11 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->defaultValue('explicit_bucket_histogram') ->end() ->end() + ->beforeNormalization()->ifTrue(function ($data): bool { + return !array_key_exists('retry', $data); + })->then(function ($data): array { + return $data + ['retry' => []]; + })->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php index 4823ed898..c732b8a51 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php @@ -34,6 +34,10 @@ final class SpanExporterOtlp implements ComponentProvider * headers: array, * compression: 'gzip'|null, * timeout: int<0, max>, + * retry: array{ + * initial_delay: int, + * max_attempts: int + * } * } $properties */ public function createPlugin(array $properties, Context $context): SpanExporterInterface @@ -46,6 +50,8 @@ public function createPlugin(array $properties, Context $context): SpanExporterI headers: $properties['headers'], compression: $properties['compression'], timeout: $properties['timeout'], + retryDelay: $properties['retry']['initial_delay'], + maxRetries: $properties['retry']['max_attempts'], cacert: $properties['certificate'], cert: $properties['client_certificate'], key: $properties['client_certificate'], @@ -67,7 +73,18 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->end() ->enumNode('compression')->values(['gzip'])->defaultNull()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() + ->arrayNode('retry') + ->children() + ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() + ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() + ->end() + ->end() ->end() + ->beforeNormalization()->ifTrue(function ($data): bool { + return !array_key_exists('retry', $data); + })->then(function ($data): array { + return $data + ['retry' => []]; + })->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php index a5665ea46..c4ea8cdb4 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php @@ -25,6 +25,10 @@ final class SpanExporterZipkin implements ComponentProvider * @param array{ * endpoint: string, * timeout: int<0, max>, + * retry: array{ + * initial_delay: int, + * max_attempts: int + * } * } $properties */ public function createPlugin(array $properties, Context $context): SpanExporterInterface @@ -33,6 +37,8 @@ public function createPlugin(array $properties, Context $context): SpanExporterI endpoint: $properties['endpoint'], contentType: 'application/json', timeout: $properties['timeout'], + retryDelay: $properties['retry']['initial_delay'], + maxRetries: $properties['retry']['max_attempts'], )); } @@ -43,7 +49,18 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->children() ->scalarNode('endpoint')->isRequired()->validate()->always(Validation::ensureString())->end()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() + ->arrayNode('retry') + ->children() + ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() + ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() + ->end() + ->end() ->end() + ->beforeNormalization()->ifTrue(function ($data): bool { + return !array_key_exists('retry', $data); + })->then(function ($data): array { + return $data + ['retry' => []]; + })->end() ; return $node; diff --git a/tests/Integration/Config/ConfigurationTest.php b/tests/Integration/Config/ConfigurationTest.php index ae204f50b..252529b94 100644 --- a/tests/Integration/Config/ConfigurationTest.php +++ b/tests/Integration/Config/ConfigurationTest.php @@ -24,5 +24,6 @@ public static function openTelemetryConfigurationDataProvider(): iterable { yield 'kitchen-sink' => [__DIR__ . '/configurations/kitchen-sink.yaml']; yield 'anchors' => [__DIR__ . '/configurations/anchors.yaml']; + yield 'minimal' => [__DIR__ . '/configurations/minimal.yaml']; } } diff --git a/tests/Integration/Config/configurations/anchors.yaml b/tests/Integration/Config/configurations/anchors.yaml index 18e409d1a..2308234e0 100644 --- a/tests/Integration/Config/configurations/anchors.yaml +++ b/tests/Integration/Config/configurations/anchors.yaml @@ -12,6 +12,9 @@ exporters: api-key: !!str 1234 compression: gzip timeout: 10000 + retry: + max_attempts: 5 + initial_delay: 100 logger_provider: processors: diff --git a/tests/Integration/Config/configurations/kitchen-sink.yaml b/tests/Integration/Config/configurations/kitchen-sink.yaml index cb82e3862..a4db4ee41 100644 --- a/tests/Integration/Config/configurations/kitchen-sink.yaml +++ b/tests/Integration/Config/configurations/kitchen-sink.yaml @@ -87,6 +87,9 @@ logger_provider: # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT timeout: 10000 + retry: + max_attempts: 5 + initial_delay: 100 # Configure log record limits. See also attribute_limits. limits: # Configure max log record attribute value size. Overrides attribute_limits.attribute_value_length_limit. @@ -155,6 +158,9 @@ meter_provider: # # Environment variable: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE temporality_preference: delta + retry: + max_attempts: 5 + initial_delay: 100 # Configure a periodic metric reader. - periodic: # Configure exporter. @@ -257,6 +263,9 @@ tracer_provider: # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT timeout: 10000 + retry: + max_attempts: 5 + initial_delay: 100 # Configure a batch span processor. - batch: # Configure exporter. @@ -273,6 +282,9 @@ tracer_provider: # # Environment variable: OTEL_EXPORTER_ZIPKIN_TIMEOUT timeout: 10000 + retry: + max_attempts: 5 + initial_delay: 200 # Configure a simple span processor. - simple: # Configure exporter. diff --git a/tests/Integration/Config/configurations/minimal.yaml b/tests/Integration/Config/configurations/minimal.yaml new file mode 100644 index 000000000..85d229bd4 --- /dev/null +++ b/tests/Integration/Config/configurations/minimal.yaml @@ -0,0 +1,35 @@ +# minimal.yaml demonstrates only providing required attributes +file_format: "0.1" +logger_provider: + processors: + - batch: + exporter: + otlp: + protocol: http/protobuf + endpoint: http://localhost:4318 + - simple: + exporter: + console: {} +meter_provider: + readers: + - periodic: + exporter: + otlp: + protocol: http/protobuf + endpoint: http://localhost:4318 +tracer_provider: + processors: + - batch: + exporter: + otlp: + protocol: http/protobuf + endpoint: http://localhost:4318 + #retry: + # initial_delay: 555 + - batch: + exporter: + zipkin: + endpoint: http://localhost:9411/api/v2/spans + - simple: + exporter: + console: {} diff --git a/tests/Unit/Config/SDK/Configuration/ExampleSdk/Trace/SpanExporterOtlp.php b/tests/Unit/Config/SDK/Configuration/ExampleSdk/Trace/SpanExporterOtlp.php index 6bd53513e..3d7ed4a0d 100644 --- a/tests/Unit/Config/SDK/Configuration/ExampleSdk/Trace/SpanExporterOtlp.php +++ b/tests/Unit/Config/SDK/Configuration/ExampleSdk/Trace/SpanExporterOtlp.php @@ -25,6 +25,8 @@ final class SpanExporterOtlp implements ComponentProvider * headers: array, * compression: 'gzip'|null, * timeout: int<0, max>, + * retryDelay: int<0, max>, + * maxRetries: int<0, max>, * } $properties */ public function createPlugin(array $properties, Context $context): SpanExporter @@ -47,6 +49,12 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->end() ->enumNode('compression')->values(['gzip'])->defaultNull()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() + ->arrayNode('retry') + ->children() + ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() + ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() + ->end() + ->end() ->end() ; diff --git a/tests/Unit/Config/SDK/Configuration/configurations/kitchen-sink.yaml b/tests/Unit/Config/SDK/Configuration/configurations/kitchen-sink.yaml index 8a8a5b800..221a07e45 100644 --- a/tests/Unit/Config/SDK/Configuration/configurations/kitchen-sink.yaml +++ b/tests/Unit/Config/SDK/Configuration/configurations/kitchen-sink.yaml @@ -293,6 +293,9 @@ tracer_provider: # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT timeout: 10000 + retry: + max_attempts: 5 + initial_delay: 100 # Configure a batch span processor. - batch: # Configure exporter. From cbaabb81c06c9de71bb2e235f7f3a66af5ead077 Mon Sep 17 00:00:00 2001 From: Brett McBride Date: Fri, 28 Jun 2024 11:18:07 +1000 Subject: [PATCH 2/4] use arrow functions --- .../SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php | 9 ++++----- .../SDK/ComponentProvider/Metrics/MetricExporterOtlp.php | 9 ++++----- .../SDK/ComponentProvider/Trace/SpanExporterOtlp.php | 9 ++++----- .../SDK/ComponentProvider/Trace/SpanExporterZipkin.php | 9 ++++----- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php index d70719e18..4e736faac 100644 --- a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php @@ -80,11 +80,10 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->end() ->end() ->end() - ->beforeNormalization()->ifTrue(function ($data): bool { - return !array_key_exists('retry', $data); - })->then(function ($data): array { - return $data + ['retry' => []]; - })->end() + ->beforeNormalization() + ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) + ->then(fn ($data): array => $data + ['retry' => []]) + ->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php index b1f344e98..a56fcfe9c 100644 --- a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php @@ -97,11 +97,10 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->defaultValue('explicit_bucket_histogram') ->end() ->end() - ->beforeNormalization()->ifTrue(function ($data): bool { - return !array_key_exists('retry', $data); - })->then(function ($data): array { - return $data + ['retry' => []]; - })->end() + ->beforeNormalization() + ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) + ->then(fn ($data): array => $data + ['retry' => []]) + ->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php index c732b8a51..0c9bc6987 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php @@ -80,11 +80,10 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->end() ->end() ->end() - ->beforeNormalization()->ifTrue(function ($data): bool { - return !array_key_exists('retry', $data); - })->then(function ($data): array { - return $data + ['retry' => []]; - })->end() + ->beforeNormalization() + ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) + ->then(fn ($data): array => $data + ['retry' => []]) + ->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php index c4ea8cdb4..bb14c0a08 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php @@ -56,11 +56,10 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->end() ->end() ->end() - ->beforeNormalization()->ifTrue(function ($data): bool { - return !array_key_exists('retry', $data); - })->then(function ($data): array { - return $data + ['retry' => []]; - })->end() + ->beforeNormalization() + ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) + ->then(fn ($data): array => $data + ['retry' => []]) + ->end() ; return $node; From 24c37b9492e7b3f051da6e791e0ebe29c8516644 Mon Sep 17 00:00:00 2001 From: Brett McBride Date: Fri, 28 Jun 2024 11:29:30 +1000 Subject: [PATCH 3/4] remove commented config --- tests/Integration/Config/configurations/minimal.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Integration/Config/configurations/minimal.yaml b/tests/Integration/Config/configurations/minimal.yaml index 85d229bd4..a25a6498b 100644 --- a/tests/Integration/Config/configurations/minimal.yaml +++ b/tests/Integration/Config/configurations/minimal.yaml @@ -24,8 +24,6 @@ tracer_provider: otlp: protocol: http/protobuf endpoint: http://localhost:4318 - #retry: - # initial_delay: 555 - batch: exporter: zipkin: From 4632de499685f3fe94e3fae061f258462d53e465 Mon Sep 17 00:00:00 2001 From: Brett McBride Date: Mon, 1 Jul 2024 12:36:12 +1000 Subject: [PATCH 4/4] use addDefaultsIfNotSet --- .../SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php | 5 +---- .../SDK/ComponentProvider/Metrics/MetricExporterOtlp.php | 5 +---- src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php | 5 +---- .../SDK/ComponentProvider/Trace/SpanExporterZipkin.php | 5 +---- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php index 4e736faac..5d2d7b303 100644 --- a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php @@ -74,16 +74,13 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->enumNode('compression')->values(['gzip'])->defaultNull()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() ->arrayNode('retry') + ->addDefaultsIfNotSet() ->children() ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() ->end() ->end() ->end() - ->beforeNormalization() - ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) - ->then(fn ($data): array => $data + ['retry' => []]) - ->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php index a56fcfe9c..e11d05cd0 100644 --- a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php @@ -83,6 +83,7 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->enumNode('compression')->values(['gzip'])->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() ->arrayNode('retry') + ->addDefaultsIfNotSet() ->children() ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() @@ -97,10 +98,6 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->defaultValue('explicit_bucket_histogram') ->end() ->end() - ->beforeNormalization() - ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) - ->then(fn ($data): array => $data + ['retry' => []]) - ->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php index 0c9bc6987..6e5ba5d08 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php @@ -74,16 +74,13 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->enumNode('compression')->values(['gzip'])->defaultNull()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() ->arrayNode('retry') + ->addDefaultsIfNotSet() ->children() ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() ->end() ->end() ->end() - ->beforeNormalization() - ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) - ->then(fn ($data): array => $data + ['retry' => []]) - ->end() ; return $node; diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php index bb14c0a08..209ea895c 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php @@ -50,16 +50,13 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->scalarNode('endpoint')->isRequired()->validate()->always(Validation::ensureString())->end()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() ->arrayNode('retry') + ->addDefaultsIfNotSet() ->children() ->integerNode('max_attempts')->min(0)->defaultValue(3)->end() ->integerNode('initial_delay')->min(0)->defaultValue(0)->end() ->end() ->end() ->end() - ->beforeNormalization() - ->ifTrue(fn ($data): bool => !array_key_exists('retry', $data)) - ->then(fn ($data): array => $data + ['retry' => []]) - ->end() ; return $node;