diff --git a/opentelemetry_distro_solarwinds/configurator.py b/opentelemetry_distro_solarwinds/configurator.py new file mode 100644 index 00000000..e795fd0f --- /dev/null +++ b/opentelemetry_distro_solarwinds/configurator.py @@ -0,0 +1,86 @@ +"""Module to initialize OpenTelemetry SDK components to work with SolarWinds backend""" + +import logging +from os import environ +from pkg_resources import ( + iter_entry_points, + load_entry_point +) + +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.response_propagator import SolarWindsTraceResponsePropagator + +logger = logging.getLogger(__name__) + +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): + environ_sampler = environ.get( + OTEL_TRACES_SAMPLER, + self._DEFAULT_SW_TRACES_SAMPLER, + ) + try: + sampler = load_entry_point( + "opentelemetry_distro_solarwinds", + "opentelemetry_traces_sampler", + environ_sampler + )() + 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: + exporter = load_entry_point( + "opentelemetry_distro_solarwinds", + "opentelemetry_traces_exporter", + environ_exporter + )() + 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..6ffab6cf 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 = [ - "tracecontext", + _TRACECONTEXT_PROPAGATOR = "tracecontext" + _SW_PROPAGATOR = "solarwinds_propagator" + _DEFAULT_SW_PROPAGATORS = [ + _TRACECONTEXT_PROPAGATOR, "baggage", - "solarwinds_propagator", + _SW_PROPAGATOR, ] + _DEFAULT_SW_TRACES_EXPORTER = "solarwinds_exporter" 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) - - # Configure context propagators to always include - # tracecontext,baggage,solarwinds -- first and in that order - # -- plus any others specified by env var + environ.setdefault(OTEL_TRACES_EXPORTER, self._DEFAULT_SW_TRACES_EXPORTER) + 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: - while default in environ_propagators: - environ_propagators.remove(default) - environ_propagators = self._DEFAULT_OTEL_PROPAGATORS + environ_propagators + # 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: + 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) - # 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) + )) 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