diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a4e061bb62..20c2b8dc9e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - unit annotations (enclosed in curly braces like `{requests}`) are stripped away - units with slash are converted e.g. `m/s` -> `meters_per_second`. - The exporter's API is not changed +- Add parameters for Distros and configurators to configure autoinstrumentation in addition to existing environment variables. + ([#3864] (https://github.com/open-telemetry/opentelemetry-python/pull/3864)) ## Version 1.24.0/0.45b0 (2024-03-28) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index ff7b04df4a4..97de4fb1001 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -50,7 +50,7 @@ MetricReader, PeriodicExportingMetricReader, ) -from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.resources import Attributes, Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter from opentelemetry.sdk.trace.id_generator import IdGenerator @@ -354,37 +354,61 @@ def _import_id_generator(id_generator_name: str) -> IdGenerator: raise RuntimeError(f"{id_generator_name} is not an IdGenerator") -def _initialize_components(auto_instrumentation_version): - trace_exporters, metric_exporters, log_exporters = _import_exporters( - _get_exporter_names("traces"), - _get_exporter_names("metrics"), - _get_exporter_names("logs"), +def _initialize_components( + auto_instrumentation_version: Optional[str] = None, + trace_exporter_names: Optional[List[str]] = None, + metric_exporter_names: Optional[List[str]] = None, + log_exporter_names: Optional[List[str]] = None, + sampler: Optional[Sampler] = None, + resource_attributes: Optional[Attributes] = None, + id_generator: IdGenerator = None, + logging_enabled: Optional[bool] = None, +): + if trace_exporter_names is None: + trace_exporter_names = [] + if metric_exporter_names is None: + metric_exporter_names = [] + if log_exporter_names is None: + log_exporter_names = [] + span_exporters, metric_exporters, log_exporters = _import_exporters( + trace_exporter_names + _get_exporter_names("traces"), + metric_exporter_names + _get_exporter_names("metrics"), + log_exporter_names + _get_exporter_names("logs"), ) - sampler_name = _get_sampler() - sampler = _import_sampler(sampler_name) - id_generator_name = _get_id_generator() - id_generator = _import_id_generator(id_generator_name) - # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name - # from the env variable else defaults to "unknown_service" - auto_resource = {} + if sampler is None: + sampler_name = _get_sampler() + sampler = _import_sampler(sampler_name) + if id_generator is None: + id_generator_name = _get_id_generator() + id_generator = _import_id_generator(id_generator_name) + if resource_attributes is None: + resource_attributes = {} # populate version if using auto-instrumentation if auto_instrumentation_version: - auto_resource[ResourceAttributes.TELEMETRY_AUTO_VERSION] = ( + resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = ( auto_instrumentation_version ) - resource = Resource.create(auto_resource) + # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name + # from the env variable else defaults to "unknown_service" + resource = Resource.create(resource_attributes) _init_tracing( - exporters=trace_exporters, + exporters=span_exporters, id_generator=id_generator, sampler=sampler, resource=resource, ) _init_metrics(metric_exporters, resource) - logging_enabled = os.getenv( - _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false" - ) - if logging_enabled.strip().lower() == "true": + if logging_enabled is None: + logging_enabled = ( + os.getenv( + _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false" + ) + .strip() + .lower() + == "true" + ) + if logging_enabled: _init_logging(log_exporters, resource) @@ -428,4 +452,4 @@ class _OTelSDKConfigurator(_BaseConfigurator): """ def _configure(self, **kwargs): - _initialize_components(kwargs.get("auto_instrumentation_version")) + _initialize_components(**kwargs) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index b825ae931cb..403b1eff005 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -40,6 +40,7 @@ _init_metrics, _init_tracing, _initialize_components, + _OTelSDKConfigurator, ) from opentelemetry.sdk._logs import LoggingHandler from opentelemetry.sdk._logs.export import ConsoleLogExporter @@ -645,7 +646,7 @@ def test_logging_init_exporter(self): @patch("opentelemetry.sdk._configuration._init_tracing") @patch("opentelemetry.sdk._configuration._init_logging") def test_logging_init_disable_default(self, logging_mock, tracing_mock): - _initialize_components("auto-version") + _initialize_components(auto_instrumentation_version="auto-version") self.assertEqual(logging_mock.call_count, 0) self.assertEqual(tracing_mock.call_count, 1) @@ -660,7 +661,7 @@ def test_logging_init_disable_default(self, logging_mock, tracing_mock): @patch("opentelemetry.sdk._configuration._init_logging") def test_logging_init_enable_env(self, logging_mock, tracing_mock): with self.assertLogs(level=WARNING): - _initialize_components("auto-version") + _initialize_components(auto_instrumentation_version="auto-version") self.assertEqual(logging_mock.call_count, 1) self.assertEqual(tracing_mock.call_count, 1) @@ -677,7 +678,7 @@ def test_logging_init_enable_env(self, logging_mock, tracing_mock): def test_initialize_components_resource( self, metrics_mock, logging_mock, tracing_mock ): - _initialize_components("auto-version") + _initialize_components(auto_instrumentation_version="auto-version") self.assertEqual(logging_mock.call_count, 1) self.assertEqual(tracing_mock.call_count, 1) self.assertEqual(metrics_mock.call_count, 1) @@ -692,6 +693,101 @@ def test_initialize_components_resource( self.assertEqual(logging_resource, metrics_resource) self.assertEqual(tracing_resource, metrics_resource) + @patch.dict( + environ, + { + "OTEL_TRACES_EXPORTER": _EXPORTER_OTLP, + "OTEL_METRICS_EXPORTER": _EXPORTER_OTLP_PROTO_GRPC, + "OTEL_LOGS_EXPORTER": _EXPORTER_OTLP_PROTO_HTTP, + }, + ) + @patch.dict( + environ, + { + "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service, custom.key.1=env-value", + "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED": "False", + }, + ) + @patch("opentelemetry.sdk._configuration.Resource") + @patch("opentelemetry.sdk._configuration._import_exporters") + @patch("opentelemetry.sdk._configuration._get_exporter_names") + @patch("opentelemetry.sdk._configuration._init_tracing") + @patch("opentelemetry.sdk._configuration._init_logging") + @patch("opentelemetry.sdk._configuration._init_metrics") + def test_initialize_components_kwargs( + self, + metrics_mock, + logging_mock, + tracing_mock, + exporter_names_mock, + import_exporters_mock, + resource_mock, + ): + exporter_names_mock.return_value = [ + "env_var_exporter_1", + "env_var_exporter_2", + ] + import_exporters_mock.return_value = ( + "TEST_SPAN_EXPORTERS_DICT", + "TEST_METRICS_EXPORTERS_DICT", + "TEST_LOG_EXPORTERS_DICT", + ) + resource_mock.create.return_value = "TEST_RESOURCE" + kwargs = { + "auto_instrumentation_version": "auto-version", + "trace_exporter_names": ["custom_span_exporter"], + "metric_exporter_names": ["custom_metric_exporter"], + "log_exporter_names": ["custom_log_exporter"], + "sampler": "TEST_SAMPLER", + "resource_attributes": { + "custom.key.1": "pass-in-value-1", + "custom.key.2": "pass-in-value-2", + }, + "id_generator": "TEST_GENERATOR", + "logging_enabled": True, + } + _initialize_components(**kwargs) + + import_exporters_mock.assert_called_once_with( + [ + "custom_span_exporter", + "env_var_exporter_1", + "env_var_exporter_2", + ], + [ + "custom_metric_exporter", + "env_var_exporter_1", + "env_var_exporter_2", + ], + [ + "custom_log_exporter", + "env_var_exporter_1", + "env_var_exporter_2", + ], + ) + resource_mock.create.assert_called_once_with( + { + "telemetry.auto.version": "auto-version", + "custom.key.1": "pass-in-value-1", + "custom.key.2": "pass-in-value-2", + } + ) + # Resource is checked separates + tracing_mock.assert_called_once_with( + exporters="TEST_SPAN_EXPORTERS_DICT", + id_generator="TEST_GENERATOR", + sampler="TEST_SAMPLER", + resource="TEST_RESOURCE", + ) + metrics_mock.assert_called_once_with( + "TEST_METRICS_EXPORTERS_DICT", + "TEST_RESOURCE", + ) + logging_mock.assert_called_once_with( + "TEST_LOG_EXPORTERS_DICT", + "TEST_RESOURCE", + ) + class TestMetricsInit(TestCase): def setUp(self): @@ -910,3 +1006,22 @@ def test__import_config_components_missing_component( str(error.value), "Requested component 'a' not found in entry point 'name'", ) + + +class TestConfigurator(TestCase): + class CustomConfigurator(_OTelSDKConfigurator): + def _configure(self, **kwargs): + kwargs["sampler"] = "TEST_SAMPLER" + super()._configure(**kwargs) + + @patch("opentelemetry.sdk._configuration._initialize_components") + def test_custom_configurator(self, mock_init_comp): + custom_configurator = TestConfigurator.CustomConfigurator() + custom_configurator._configure( + auto_instrumentation_version="TEST_VERSION2" + ) + kwargs = { + "auto_instrumentation_version": "TEST_VERSION2", + "sampler": "TEST_SAMPLER", + } + mock_init_comp.assert_called_once_with(**kwargs)