From 7b75e65f4df0a784060b072472c0f75fa0fa9081 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Fri, 22 Apr 2022 12:15:39 -0700 Subject: [PATCH 1/4] Separate concerns: env vars in distro, component init in configurator --- .../configurator.py | 82 +++++++++++++++++ opentelemetry_distro_solarwinds/distro.py | 87 ++++++------------- setup.cfg | 6 +- 3 files changed, 113 insertions(+), 62 deletions(-) create mode 100644 opentelemetry_distro_solarwinds/configurator.py diff --git a/opentelemetry_distro_solarwinds/configurator.py b/opentelemetry_distro_solarwinds/configurator.py new file mode 100644 index 00000000..0face11f --- /dev/null +++ b/opentelemetry_distro_solarwinds/configurator.py @@ -0,0 +1,82 @@ +"""Module to initialize OpenTelemetry SDK components to work with SolarWinds backend""" + +import logging +from os import environ +from pkg_resources import iter_entry_points + +from opentelemetry import trace +from opentelemetry.environment_variables import ( + OTEL_PROPAGATORS, + OTEL_TRACES_EXPORTER +) +from opentelemetry.instrumentation.propagators import set_global_response_propagator +from opentelemetry.propagate import set_global_textmap +from opentelemetry.propagators.composite import CompositePropagator +from opentelemetry.sdk._configuration import _OTelSDKConfigurator +from opentelemetry.sdk.environment_variables import OTEL_TRACES_SAMPLER +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor + +from opentelemetry_distro_solarwinds.distro import SolarWindsDistro +from opentelemetry_distro_solarwinds.response_propagator import SolarWindsTraceResponsePropagator + +logger = logging.getLogger(__name__) + +class SolarWindsConfigurator(_OTelSDKConfigurator): + """OpenTelemetry Configurator for initializing SolarWinds-reporting SDK components""" + + def _configure(self, **kwargs): + # If default_traces_sampler is configured then hook up + # Else let OTel Python get_from_env_or_default + environ_sampler = environ.get(OTEL_TRACES_SAMPLER) + if environ_sampler == SolarWindsDistro.default_sw_traces_sampler(): + try: + sampler = next( + iter_entry_points( + "opentelemetry_traces_sampler", + environ_sampler + )).load()() + except: + logger.exception( + "Failed to load configured sampler `%s`", environ_sampler + ) + raise + trace.set_tracer_provider( + TracerProvider(sampler=sampler)) + else: + trace.set_tracer_provider() + + environ_exporter = environ.get(OTEL_TRACES_EXPORTER) + try: + exporter = next( + iter_entry_points( + "opentelemetry_traces_exporter", + environ_exporter + )).load()() + except: + logger.exception( + "Failed to load configured exporter `%s`", environ_exporter + ) + raise + span_exporter = BatchSpanProcessor(exporter) + trace.get_tracer_provider().add_span_processor(span_exporter) + + # Init and set CompositePropagator globally, like OTel API + environ_propagators = environ.get(OTEL_PROPAGATORS).split(",") + propagators = [] + for propagator in environ_propagators: + try: + propagators.append( + next( + iter_entry_points("opentelemetry_propagator", propagator) + ).load()() + ) + except Exception: + logger.exception( + "Failed to load configured propagator `%s`", propagator + ) + raise + set_global_textmap(CompositePropagator(propagators)) + + # Set global HTTP response propagator + set_global_response_propagator(SolarWindsTraceResponsePropagator()) diff --git a/opentelemetry_distro_solarwinds/distro.py b/opentelemetry_distro_solarwinds/distro.py index cbd35c55..f48f8672 100644 --- a/opentelemetry_distro_solarwinds/distro.py +++ b/opentelemetry_distro_solarwinds/distro.py @@ -1,87 +1,52 @@ -"""Module to configure OpenTelemetry agent to work with SolarWinds backend""" +"""Module to configure OpenTelemetry to work with SolarWinds backend""" import logging from os import environ -from pkg_resources import iter_entry_points -from opentelemetry import trace -from opentelemetry.environment_variables import OTEL_PROPAGATORS, OTEL_TRACES_EXPORTER +from opentelemetry.environment_variables import ( + OTEL_PROPAGATORS, + OTEL_TRACES_EXPORTER +) from opentelemetry.instrumentation.distro import BaseDistro -from opentelemetry.instrumentation.propagators import set_global_response_propagator -from opentelemetry.propagate import set_global_textmap -from opentelemetry.propagators.composite import CompositePropagator -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor - -from opentelemetry_distro_solarwinds.response_propagator import SolarWindsTraceResponsePropagator -from opentelemetry_distro_solarwinds.sampler import ParentBasedSwSampler +from opentelemetry.sdk.environment_variables import OTEL_TRACES_SAMPLER logger = logging.getLogger(__name__) class SolarWindsDistro(BaseDistro): - """SolarWinds custom distro for OpenTelemetry agents.""" + """OpenTelemetry Distro for SolarWinds reporting environment""" - _DEFAULT_OTEL_EXPORTER = "solarwinds_exporter" - _DEFAULT_OTEL_PROPAGATORS = [ + _DEFAULT_SW_PROPAGATORS = [ "tracecontext", "baggage", "solarwinds_propagator", ] + _DEFAULT_SW_TRACES_EXPORTER = "solarwinds_exporter" + _DEFAULT_SW_TRACES_SAMPLER = "solarwinds_sampler" def _configure(self, **kwargs): - # Automatically use custom SolarWinds sampler - trace.set_tracer_provider( - TracerProvider(sampler=ParentBasedSwSampler())) - - # Customize Exporter else default to SolarWindsSpanExporter - environ_exporter = environ.get( - OTEL_TRACES_EXPORTER, - self._DEFAULT_OTEL_EXPORTER - ) - try: - exporter = next( - iter_entry_points( - "opentelemetry_traces_exporter", - environ_exporter - )).load()() - except: - logger.exception( - "Failed to load configured exporter `%s`", environ_exporter - ) - raise - - span_exporter = BatchSpanProcessor(exporter) - trace.get_tracer_provider().add_span_processor(span_exporter) - + environ.setdefault(OTEL_TRACES_SAMPLER, self._DEFAULT_SW_TRACES_SAMPLER) + environ.setdefault(OTEL_TRACES_EXPORTER, self._DEFAULT_SW_TRACES_EXPORTER) + # Configure context propagators to always include # tracecontext,baggage,solarwinds -- first and in that order # -- plus any others specified by env var environ_propagators = environ.get( OTEL_PROPAGATORS, - ",".join(self._DEFAULT_OTEL_PROPAGATORS) + ",".join(self._DEFAULT_SW_PROPAGATORS) ).split(",") - if environ_propagators != self._DEFAULT_OTEL_PROPAGATORS: - for default in self._DEFAULT_OTEL_PROPAGATORS: + if environ_propagators != self._DEFAULT_SW_PROPAGATORS: + for default in self._DEFAULT_SW_PROPAGATORS: while default in environ_propagators: environ_propagators.remove(default) - environ_propagators = self._DEFAULT_OTEL_PROPAGATORS + environ_propagators + environ_propagators = self._DEFAULT_SW_PROPAGATORS + environ_propagators environ[OTEL_PROPAGATORS] = ",".join(environ_propagators) - # Init and set CompositePropagator globally, like OTel API - propagators = [] - for propagator in environ_propagators: - try: - propagators.append( - next( - iter_entry_points("opentelemetry_propagator", propagator) - ).load()() - ) - except Exception: - logger.exception( - "Failed to load configured propagator `%s`", propagator - ) - raise - set_global_textmap(CompositePropagator(propagators)) - - # Set global HTTP response propagator - set_global_response_propagator(SolarWindsTraceResponsePropagator()) \ No newline at end of file + logger.debug("Configured SolarWindsDistro: {}, {}, {}".format( + environ.get(OTEL_TRACES_SAMPLER), + environ.get(OTEL_TRACES_EXPORTER), + environ.get(OTEL_PROPAGATORS) + )) + + @classmethod + def default_sw_traces_sampler(cls): + return cls._DEFAULT_SW_TRACES_SAMPLER diff --git a/setup.cfg b/setup.cfg index 3ee83a83..50733850 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,7 +25,11 @@ install_requires = [options.entry_points] opentelemetry_distro = solarwinds_distro = opentelemetry_distro_solarwinds.distro:SolarWindsDistro +opentelemetry_configurator = + solarwinds_configurator = opentelemetry_distro_solarwinds.configurator:SolarWindsConfigurator opentelemetry_propagator = solarwinds_propagator = opentelemetry_distro_solarwinds.propagator:SolarWindsPropagator opentelemetry_traces_exporter = - solarwinds_exporter = opentelemetry_distro_solarwinds.exporter:SolarWindsSpanExporter \ No newline at end of file + solarwinds_exporter = opentelemetry_distro_solarwinds.exporter:SolarWindsSpanExporter +opentelemetry_traces_sampler = + solarwinds_sampler = opentelemetry_distro_solarwinds.sampler:ParentBasedSwSampler \ No newline at end of file From 2842209ef754e51cc38eb069336e09f7d7f8d1b3 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Thu, 28 Apr 2022 12:38:43 -0700 Subject: [PATCH 2/4] NH-2313 adjust CompositePropagator config with OTEL_PROPAGATORS --- opentelemetry_distro_solarwinds/distro.py | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/opentelemetry_distro_solarwinds/distro.py b/opentelemetry_distro_solarwinds/distro.py index f48f8672..89073868 100644 --- a/opentelemetry_distro_solarwinds/distro.py +++ b/opentelemetry_distro_solarwinds/distro.py @@ -15,10 +15,12 @@ class SolarWindsDistro(BaseDistro): """OpenTelemetry Distro for SolarWinds reporting environment""" + _TRACECONTEXT_PROPAGATOR = "tracecontext" + _SW_PROPAGATOR = "solarwinds_propagator" _DEFAULT_SW_PROPAGATORS = [ - "tracecontext", + _TRACECONTEXT_PROPAGATOR, "baggage", - "solarwinds_propagator", + _SW_PROPAGATOR, ] _DEFAULT_SW_TRACES_EXPORTER = "solarwinds_exporter" _DEFAULT_SW_TRACES_SAMPLER = "solarwinds_sampler" @@ -27,18 +29,22 @@ def _configure(self, **kwargs): environ.setdefault(OTEL_TRACES_SAMPLER, self._DEFAULT_SW_TRACES_SAMPLER) environ.setdefault(OTEL_TRACES_EXPORTER, self._DEFAULT_SW_TRACES_EXPORTER) - # Configure context propagators to always include - # tracecontext,baggage,solarwinds -- first and in that order - # -- plus any others specified by env var environ_propagators = environ.get( OTEL_PROPAGATORS, ",".join(self._DEFAULT_SW_PROPAGATORS) ).split(",") + # If not using the default propagators, + # can any arbitrary list BUT + # (1) must include tracecontext and solarwinds_propagator + # (2) tracecontext must be before solarwinds_propagator if environ_propagators != self._DEFAULT_SW_PROPAGATORS: - for default in self._DEFAULT_SW_PROPAGATORS: - while default in environ_propagators: - environ_propagators.remove(default) - environ_propagators = self._DEFAULT_SW_PROPAGATORS + environ_propagators + if not self._TRACECONTEXT_PROPAGATOR in environ_propagators or \ + not self._SW_PROPAGATOR in environ_propagators: + raise ValueError("Must include tracecontext and solarwinds_propagator in OTEL_PROPAGATORS to use SolarWinds Observability.") + + if environ_propagators.index(self._SW_PROPAGATOR) \ + < environ_propagators.index(self._TRACECONTEXT_PROPAGATOR): + raise ValueError("tracecontext must be before solarwinds_propagator in OTEL_PROPAGATORS to use SolarWinds Observability.") environ[OTEL_PROPAGATORS] = ",".join(environ_propagators) logger.debug("Configured SolarWindsDistro: {}, {}, {}".format( From 7ffb1a9bee831c23fba0ddee4c5116e63de021c4 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Thu, 28 Apr 2022 16:16:01 -0700 Subject: [PATCH 3/4] solarwinds_sampler cannot be env default because not in OTel _KNOWN_SAMPLERS --- .../configurator.py | 41 ++++++++++--------- opentelemetry_distro_solarwinds/distro.py | 6 --- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/opentelemetry_distro_solarwinds/configurator.py b/opentelemetry_distro_solarwinds/configurator.py index 0face11f..7a5514e7 100644 --- a/opentelemetry_distro_solarwinds/configurator.py +++ b/opentelemetry_distro_solarwinds/configurator.py @@ -17,7 +17,6 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor -from opentelemetry_distro_solarwinds.distro import SolarWindsDistro from opentelemetry_distro_solarwinds.response_propagator import SolarWindsTraceResponsePropagator logger = logging.getLogger(__name__) @@ -25,26 +24,28 @@ class SolarWindsConfigurator(_OTelSDKConfigurator): """OpenTelemetry Configurator for initializing SolarWinds-reporting SDK components""" + # Cannot set as env default because not part of OTel Python _KNOWN_SAMPLERS + # https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-sdk/src/opentelemetry/sdk/trace/sampling.py#L364-L380 + _DEFAULT_SW_TRACES_SAMPLER = "solarwinds_sampler" + def _configure(self, **kwargs): - # If default_traces_sampler is configured then hook up - # Else let OTel Python get_from_env_or_default - environ_sampler = environ.get(OTEL_TRACES_SAMPLER) - if environ_sampler == SolarWindsDistro.default_sw_traces_sampler(): - try: - sampler = next( - iter_entry_points( - "opentelemetry_traces_sampler", - environ_sampler - )).load()() - except: - logger.exception( - "Failed to load configured sampler `%s`", environ_sampler - ) - raise - trace.set_tracer_provider( - TracerProvider(sampler=sampler)) - else: - trace.set_tracer_provider() + environ_sampler = environ.get( + OTEL_TRACES_SAMPLER, + self._DEFAULT_SW_TRACES_SAMPLER, + ) + try: + sampler = next( + iter_entry_points( + "opentelemetry_traces_sampler", + environ_sampler + )).load()() + except: + logger.exception( + "Failed to load configured sampler `%s`", environ_sampler + ) + raise + trace.set_tracer_provider( + TracerProvider(sampler=sampler)) environ_exporter = environ.get(OTEL_TRACES_EXPORTER) try: diff --git a/opentelemetry_distro_solarwinds/distro.py b/opentelemetry_distro_solarwinds/distro.py index f48f8672..0b3371b4 100644 --- a/opentelemetry_distro_solarwinds/distro.py +++ b/opentelemetry_distro_solarwinds/distro.py @@ -21,10 +21,8 @@ class SolarWindsDistro(BaseDistro): "solarwinds_propagator", ] _DEFAULT_SW_TRACES_EXPORTER = "solarwinds_exporter" - _DEFAULT_SW_TRACES_SAMPLER = "solarwinds_sampler" def _configure(self, **kwargs): - environ.setdefault(OTEL_TRACES_SAMPLER, self._DEFAULT_SW_TRACES_SAMPLER) environ.setdefault(OTEL_TRACES_EXPORTER, self._DEFAULT_SW_TRACES_EXPORTER) # Configure context propagators to always include @@ -46,7 +44,3 @@ def _configure(self, **kwargs): environ.get(OTEL_TRACES_EXPORTER), environ.get(OTEL_PROPAGATORS) )) - - @classmethod - def default_sw_traces_sampler(cls): - return cls._DEFAULT_SW_TRACES_SAMPLER From 1241e7a149facd9f8b84f274e8d3e760ffb638e5 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 3 May 2022 12:19:23 -0700 Subject: [PATCH 4/4] load_entry_point instead of iter for singulars --- .../configurator.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/opentelemetry_distro_solarwinds/configurator.py b/opentelemetry_distro_solarwinds/configurator.py index 7a5514e7..e795fd0f 100644 --- a/opentelemetry_distro_solarwinds/configurator.py +++ b/opentelemetry_distro_solarwinds/configurator.py @@ -2,7 +2,10 @@ import logging from os import environ -from pkg_resources import iter_entry_points +from pkg_resources import ( + iter_entry_points, + load_entry_point +) from opentelemetry import trace from opentelemetry.environment_variables import ( @@ -34,11 +37,11 @@ def _configure(self, **kwargs): self._DEFAULT_SW_TRACES_SAMPLER, ) try: - sampler = next( - iter_entry_points( - "opentelemetry_traces_sampler", - environ_sampler - )).load()() + sampler = load_entry_point( + "opentelemetry_distro_solarwinds", + "opentelemetry_traces_sampler", + environ_sampler + )() except: logger.exception( "Failed to load configured sampler `%s`", environ_sampler @@ -49,11 +52,11 @@ def _configure(self, **kwargs): environ_exporter = environ.get(OTEL_TRACES_EXPORTER) try: - exporter = next( - iter_entry_points( - "opentelemetry_traces_exporter", - environ_exporter - )).load()() + exporter = load_entry_point( + "opentelemetry_distro_solarwinds", + "opentelemetry_traces_exporter", + environ_exporter + )() except: logger.exception( "Failed to load configured exporter `%s`", environ_exporter