From f1c982734b8971ac3aa203eaee6d1ca1bb3d56e1 Mon Sep 17 00:00:00 2001 From: Brett McBride Date: Tue, 26 Nov 2024 11:48:31 +1100 Subject: [PATCH] add sdk-config 0.3 support for key/value headers and attributes (#1428) - headers and attributes are now arrays with `key` and `value` elements - headers can optionally contain a `headers_list` element which supports a lower-priority CSV of key/value entries - attributes can optionally contain an `attributes_list` element which supports a lower-priority CSV of key/value/data-type entries --- examples/load_config.yaml | 4 +- examples/load_config_env.yaml | 3 +- .../Logs/LogRecordExporterOtlp.php | 16 ++++- .../Metrics/MetricExporterOtlp.php | 16 ++++- .../ComponentProvider/OpenTelemetrySdk.php | 18 ++++- .../Trace/SpanExporterOtlp.php | 16 ++++- .../Config/configurations/anchors.yaml | 3 +- .../Config/configurations/kitchen-sink.yaml | 68 +++++++++++++------ 8 files changed, 110 insertions(+), 34 deletions(-) diff --git a/examples/load_config.yaml b/examples/load_config.yaml index d4d8d4de4..0535a6dbb 100644 --- a/examples/load_config.yaml +++ b/examples/load_config.yaml @@ -2,7 +2,9 @@ file_format: '0.3' resource: attributes: - service.name: opentelemetry-demo + - name: service.name + value: opentelemetry-demo + attributes_list: service.name=unused,example.foo=foo_value,example.bar=bar_value propagators: composite: [ tracecontext, baggage ] diff --git a/examples/load_config_env.yaml b/examples/load_config_env.yaml index b593419be..ba760b228 100644 --- a/examples/load_config_env.yaml +++ b/examples/load_config_env.yaml @@ -4,7 +4,8 @@ disabled: ${OTEL_SDK_DISABLED} resource: attributes: - service.name: ${OTEL_SERVICE_NAME} + - name: service.name + value: ${OTEL_SERVICE_NAME} propagators: composite: [ tracecontext, baggage ] diff --git a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php index 6ee171296..4a829797b 100644 --- a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php @@ -13,6 +13,7 @@ use OpenTelemetry\Contrib\Otlp\LogsExporter; use OpenTelemetry\Contrib\Otlp\OtlpUtil; use OpenTelemetry\Contrib\Otlp\Protocols; +use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser; use OpenTelemetry\SDK\Logs\LogRecordExporterInterface; use OpenTelemetry\SDK\Registry; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; @@ -31,7 +32,8 @@ final class LogRecordExporterOtlp implements ComponentProvider * certificate: ?string, * client_key: ?string, * client_certificate: ?string, - * headers: array, + * headers: list, + * headers_list: ?string, * compression: 'gzip'|null, * timeout: int<0, max>, * } $properties @@ -40,10 +42,12 @@ public function createPlugin(array $properties, Context $context): LogRecordExpo { $protocol = $properties['protocol']; + $headers = array_column($properties['headers'], 'value', 'name') + MapParser::parse($properties['headers_list']); + return new LogsExporter(Registry::transportFactory($protocol)->create( endpoint: $properties['endpoint'] . OtlpUtil::path(Signals::LOGS, $protocol), contentType: Protocols::contentType($protocol), - headers: $properties['headers'], + headers: $headers, compression: $properties['compression'], timeout: $properties['timeout'], cacert: $properties['certificate'], @@ -63,8 +67,14 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->scalarNode('client_key')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->scalarNode('client_certificate')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->arrayNode('headers') - ->scalarPrototype()->end() + ->arrayPrototype() + ->children() + ->scalarNode('name')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('value')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() + ->end() + ->end() ->end() + ->scalarNode('headers_list')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->enumNode('compression')->values(['gzip'])->defaultNull()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() ->end() diff --git a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php index b94015b26..6e566304e 100644 --- a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php @@ -13,6 +13,7 @@ use OpenTelemetry\Contrib\Otlp\MetricExporter; use OpenTelemetry\Contrib\Otlp\OtlpUtil; use OpenTelemetry\Contrib\Otlp\Protocols; +use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser; use OpenTelemetry\SDK\Metrics\Data\Temporality; use OpenTelemetry\SDK\Metrics\MetricExporterInterface; use OpenTelemetry\SDK\Registry; @@ -32,7 +33,8 @@ final class MetricExporterOtlp implements ComponentProvider * certificate: ?string, * client_key: ?string, * client_certificate: ?string, - * headers: array, + * headers: list, + * headers_list: ?string, * compression: 'gzip'|null, * timeout: int<0, max>, * temporality_preference: 'cumulative'|'delta'|'lowmemory', @@ -43,6 +45,8 @@ public function createPlugin(array $properties, Context $context): MetricExporte { $protocol = $properties['protocol']; + $headers = array_column($properties['headers'], 'value', 'name') + MapParser::parse($properties['headers_list']); + $temporality = match ($properties['temporality_preference']) { 'cumulative' => Temporality::CUMULATIVE, 'delta' => Temporality::DELTA, @@ -52,7 +56,7 @@ public function createPlugin(array $properties, Context $context): MetricExporte return new MetricExporter(Registry::transportFactory($protocol)->create( endpoint: $properties['endpoint'] . OtlpUtil::path(Signals::METRICS, $protocol), contentType: Protocols::contentType($protocol), - headers: $properties['headers'], + headers: $headers, compression: $properties['compression'], timeout: $properties['timeout'], cacert: $properties['certificate'], @@ -72,8 +76,14 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->scalarNode('client_key')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->scalarNode('client_certificate')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->arrayNode('headers') - ->scalarPrototype()->end() + ->arrayPrototype() + ->children() + ->scalarNode('name')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('value')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() + ->end() + ->end() ->end() + ->scalarNode('headers_list')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->enumNode('compression')->values(['gzip'])->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() ->enumNode('temporality_preference') diff --git a/src/Config/SDK/ComponentProvider/OpenTelemetrySdk.php b/src/Config/SDK/ComponentProvider/OpenTelemetrySdk.php index 3cd1c1b44..cae7ec901 100644 --- a/src/Config/SDK/ComponentProvider/OpenTelemetrySdk.php +++ b/src/Config/SDK/ComponentProvider/OpenTelemetrySdk.php @@ -13,6 +13,7 @@ use OpenTelemetry\Context\Propagation\NoopTextMapPropagator; use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface; use OpenTelemetry\SDK\Common\Attribute\Attributes; +use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser; use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory; use OpenTelemetry\SDK\Logs\EventLoggerProvider; use OpenTelemetry\SDK\Logs\LoggerProvider; @@ -56,6 +57,7 @@ final class OpenTelemetrySdk implements ComponentProvider * disabled: bool, * resource: array{ * attributes: array, + * attributes_list: ?string, * schema_url: ?string, * }, * attribute_limits: array{ @@ -113,10 +115,10 @@ public function createPlugin(array $properties, Context $context): SdkBuilder if ($properties['disabled']) { return $sdkBuilder; } - + $attributes = array_column($properties['resource']['attributes'], 'value', 'name') + MapParser::parse($properties['resource']['attributes_list']); $resource = ResourceInfoFactory::defaultResource() ->merge(ResourceInfo::create( - attributes: Attributes::create($properties['resource']['attributes']), + attributes: Attributes::create($attributes), schemaUrl: $properties['resource']['schema_url'], )); @@ -284,8 +286,18 @@ private function getResourceConfig(): ArrayNodeDefinition ->addDefaultsIfNotSet() ->children() ->arrayNode('attributes') - ->variablePrototype()->end() + ->arrayPrototype() + ->children() + ->scalarNode('name')->isRequired()->end() + ->variableNode('value')->isRequired()->end() + // @todo use type to validate and/or cast attributes + ->enumNode('type')->defaultNull() + ->values(['string', 'bool', 'int', 'double', 'string_array', 'bool_array', 'int_array', 'double_array']) + ->end() + ->end() + ->end() ->end() + ->scalarNode('attributes_list')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->scalarNode('schema_url')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->end(); diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php index 4823ed898..32e2c0521 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php @@ -13,6 +13,7 @@ use OpenTelemetry\Contrib\Otlp\OtlpUtil; use OpenTelemetry\Contrib\Otlp\Protocols; use OpenTelemetry\Contrib\Otlp\SpanExporter; +use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser; use OpenTelemetry\SDK\Registry; use OpenTelemetry\SDK\Trace\SpanExporterInterface; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; @@ -31,7 +32,8 @@ final class SpanExporterOtlp implements ComponentProvider * certificate: ?string, * client_key: ?string, * client_certificate: ?string, - * headers: array, + * headers: list, + * headers_list: ?string, * compression: 'gzip'|null, * timeout: int<0, max>, * } $properties @@ -40,10 +42,12 @@ public function createPlugin(array $properties, Context $context): SpanExporterI { $protocol = $properties['protocol']; + $headers = array_column($properties['headers'], 'value', 'name') + MapParser::parse($properties['headers_list']); + return new SpanExporter(Registry::transportFactory($protocol)->create( endpoint: $properties['endpoint'] . OtlpUtil::path(Signals::TRACE, $protocol), contentType: Protocols::contentType($protocol), - headers: $properties['headers'], + headers: $headers, compression: $properties['compression'], timeout: $properties['timeout'], cacert: $properties['certificate'], @@ -63,8 +67,14 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit ->scalarNode('client_key')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->scalarNode('client_certificate')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->arrayNode('headers') - ->scalarPrototype()->end() + ->arrayPrototype() + ->children() + ->scalarNode('name')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('value')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() + ->end() + ->end() ->end() + ->scalarNode('headers_list')->defaultNull()->validate()->always(Validation::ensureString())->end()->end() ->enumNode('compression')->values(['gzip'])->defaultNull()->end() ->integerNode('timeout')->min(0)->defaultValue(10)->end() ->end() diff --git a/tests/Integration/Config/configurations/anchors.yaml b/tests/Integration/Config/configurations/anchors.yaml index 1d821aeac..ea6121ac1 100644 --- a/tests/Integration/Config/configurations/anchors.yaml +++ b/tests/Integration/Config/configurations/anchors.yaml @@ -9,7 +9,8 @@ exporters: client_key: /app/cert.pem client_certificate: /app/cert.pem headers: - api-key: !!str 1234 + - name: api-key + value: "str 1234" compression: gzip timeout: 10000 diff --git a/tests/Integration/Config/configurations/kitchen-sink.yaml b/tests/Integration/Config/configurations/kitchen-sink.yaml index f1528e98e..e0dcaf557 100644 --- a/tests/Integration/Config/configurations/kitchen-sink.yaml +++ b/tests/Integration/Config/configurations/kitchen-sink.yaml @@ -74,11 +74,13 @@ logger_provider: # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE client_certificate: /app/cert.pem - # Configure headers. - # - # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_LOGS_HEADERS + # Configure headers. Entries have higher priority than entries from .headers_list. headers: - api-key: "1234" + - name: api-key + value: "1234" + # Configure headers. Entries have lower priority than entries from .headers. + # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. + headers_list: "api-key=1234" # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION @@ -138,11 +140,13 @@ meter_provider: # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE client_certificate: /app/cert.pem - # Configure headers. - # - # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_METRICS_HEADERS + # Configure headers. Entries have higher priority than entries from .headers_list. headers: - api-key: !!str 1234 + - name: api-key + value: "1234" + # Configure headers. Entries have lower priority than entries from .headers. + # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. + headers_list: "api-key=1234" # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION @@ -244,11 +248,13 @@ tracer_provider: # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE client_certificate: /app/cert.pem - # Configure headers. - # - # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS + # Configure headers. Entries have higher priority than entries from .headers_list. headers: - api-key: !!str 1234 + - name: api-key + value: "1234" + # Configure headers. Entries have lower priority than entries from .headers. + # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. + headers_list: "api-key=1234" # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION @@ -340,14 +346,38 @@ tracer_provider: # Configure resource for all signals. resource: - # Configure resource attributes. - # - # Environment variable: OTEL_RESOURCE_ATTRIBUTES + # Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. + # Entries must contain .name nand .value, and may optionally include .type, which defaults ot "string" if not set. The value must match the type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. attributes: - # Configure `service.name` resource attribute - # - # Environment variable: OTEL_SERVICE_NAME - service.name: !!str "unknown_service" + - name: service.name + value: unknown_service + - name: string_key + value: value + type: string + - name: bool_key + value: true + type: bool + - name: int_key + value: 1 + type: int + - name: double_key + value: 1.1 + type: double + - name: string_array_key + value: [ "value1", "value2" ] + type: string_array + - name: bool_array_key + value: [ true, false ] + type: bool_array + - name: int_array_key + value: [ 1, 2 ] + type: int_array + - name: double_array_key + value: [ 1.1, 2.2 ] + type: double_array + # Configure resource attributes. Entries have lower priority than entries from .resource.attributes. + # The value is a list of comma separated key-value pairs matching the format of OTEL_RESOURCE_ATTRIBUTES. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration for details. + attributes_list: "service.namespace=my-namespace,service.version=1.0.0" # Configure the resource schema URL. schema_url: https://opentelemetry.io/schemas/1.27.0