diff --git a/opentelemetry_distro_solarwinds/__init__.py b/opentelemetry_distro_solarwinds/__init__.py index f5341bd4..8c04e25f 100644 --- a/opentelemetry_distro_solarwinds/__init__.py +++ b/opentelemetry_distro_solarwinds/__init__.py @@ -6,4 +6,5 @@ EQUALS_W3C_SANITIZED = "####" SW_TRACESTATE_KEY = "sw" OTEL_CONTEXT_SW_OPTIONS_KEY = "sw_xtraceoptions" -OTEL_CONTEXT_SW_SIGNATURE_KEY = "sw_signature" \ No newline at end of file +OTEL_CONTEXT_SW_SIGNATURE_KEY = "sw_signature" +DEFAULT_SW_TRACES_EXPORTER = "solarwinds_exporter" \ No newline at end of file diff --git a/opentelemetry_distro_solarwinds/configurator.py b/opentelemetry_distro_solarwinds/configurator.py index e795fd0f..2a06f7e2 100644 --- a/opentelemetry_distro_solarwinds/configurator.py +++ b/opentelemetry_distro_solarwinds/configurator.py @@ -17,9 +17,14 @@ 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 import ( + sampling, + TracerProvider +) from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry_distro_solarwinds import DEFAULT_SW_TRACES_EXPORTER +from opentelemetry_distro_solarwinds.extension.oboe import Reporter from opentelemetry_distro_solarwinds.response_propagator import SolarWindsTraceResponsePropagator logger = logging.getLogger(__name__) @@ -32,55 +37,120 @@ class SolarWindsConfigurator(_OTelSDKConfigurator): _DEFAULT_SW_TRACES_SAMPLER = "solarwinds_sampler" def _configure(self, **kwargs): - environ_sampler = environ.get( - OTEL_TRACES_SAMPLER, - self._DEFAULT_SW_TRACES_SAMPLER, - ) + """Configure OTel sampler, exporter, propagator, response propagator""" + reporter = self._initialize_solarwinds_reporter() + self._configure_sampler() + self._configure_exporter(reporter) + self._configure_propagator() + # Set global HTTP response propagator + set_global_response_propagator(SolarWindsTraceResponsePropagator()) + + def _configure_sampler(self): + """Always configure SolarWinds OTel sampler""" try: sampler = load_entry_point( "opentelemetry_distro_solarwinds", "opentelemetry_traces_sampler", - environ_sampler + self._DEFAULT_SW_TRACES_SAMPLER )() except: logger.exception( - "Failed to load configured sampler `%s`", environ_sampler + "Failed to load configured sampler {}".format( + self._DEFAULT_SW_TRACES_SAMPLER + ) ) raise trace.set_tracer_provider( - TracerProvider(sampler=sampler)) + 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) + def _configure_exporter(self, reporter): + """Configure SolarWinds or env-specified OTel span exporter. + Initialization of SolarWinds exporter requires a liboboe reporter.""" + exporter = None + environ_exporter_name = environ.get(OTEL_TRACES_EXPORTER) - # Init and set CompositePropagator globally, like OTel API - environ_propagators = environ.get(OTEL_PROPAGATORS).split(",") + if environ_exporter_name == DEFAULT_SW_TRACES_EXPORTER: + try: + exporter = load_entry_point( + "opentelemetry_distro_solarwinds", + "opentelemetry_traces_exporter", + environ_exporter_name + )(reporter) + except: + logger.exception( + "Failed to load configured exporter {} with reporter".format( + environ_exporter_name + ) + ) + raise + else: + try: + exporter = next( + iter_entry_points( + "opentelemetry_traces_exporter", + environ_exporter_name + ) + ).load()() + except: + logger.exception( + "Failed to load configured exporter {}".format( + environ_exporter_name + ) + ) + raise + span_processor = BatchSpanProcessor(exporter) + trace.get_tracer_provider().add_span_processor(span_processor) + + def _configure_propagator(self): + """Configure CompositePropagator with SolarWinds and other propagators""" propagators = [] - for propagator in environ_propagators: + environ_propagators_names = environ.get(OTEL_PROPAGATORS).split(",") + for propagator_name in environ_propagators_names: try: propagators.append( next( - iter_entry_points("opentelemetry_propagator", propagator) + iter_entry_points("opentelemetry_propagator", propagator_name) ).load()() ) except Exception: logger.exception( - "Failed to load configured propagator `%s`", propagator + "Failed to load configured propagator {}".format( + propagator_name + ) ) raise set_global_textmap(CompositePropagator(propagators)) - # Set global HTTP response propagator - set_global_response_propagator(SolarWindsTraceResponsePropagator()) + def _initialize_solarwinds_reporter(self) -> Reporter: + """Initialize SolarWinds reporter used by sampler and exporter. This establishes collector and sampling settings in a background thread.""" + log_level = environ.get('SOLARWINDS_DEBUG_LEVEL', 3) + try: + log_level = int(log_level) + except ValueError: + log_level = 3 + # TODO make some of these customizable + return Reporter( + hostname_alias='', + log_level=log_level, + log_file_path='', + max_transactions=-1, + max_flush_wait_time=-1, + events_flush_interval=-1, + max_request_size_bytes=-1, + reporter='ssl', + host=environ.get('SOLARWINDS_COLLECTOR', ''), + service_key=environ.get('SOLARWINDS_SERVICE_KEY', ''), + trusted_path='', + buffer_size=-1, + trace_metrics=-1, + histogram_precision=-1, + token_bucket_capacity=-1, + token_bucket_rate=-1, + file_single=0, + ec2_metadata_timeout=1000, + grpc_proxy='', + stdout_clear_nonblocking=0, + is_grpc_clean_hack_enabled=False, + w3c_trace_format=1, + ) diff --git a/opentelemetry_distro_solarwinds/distro.py b/opentelemetry_distro_solarwinds/distro.py index 6ffab6cf..be0a2b27 100644 --- a/opentelemetry_distro_solarwinds/distro.py +++ b/opentelemetry_distro_solarwinds/distro.py @@ -8,7 +8,8 @@ OTEL_TRACES_EXPORTER ) from opentelemetry.instrumentation.distro import BaseDistro -from opentelemetry.sdk.environment_variables import OTEL_TRACES_SAMPLER + +from opentelemetry_distro_solarwinds import DEFAULT_SW_TRACES_EXPORTER logger = logging.getLogger(__name__) @@ -22,10 +23,10 @@ class SolarWindsDistro(BaseDistro): "baggage", _SW_PROPAGATOR, ] - _DEFAULT_SW_TRACES_EXPORTER = "solarwinds_exporter" def _configure(self, **kwargs): - environ.setdefault(OTEL_TRACES_EXPORTER, self._DEFAULT_SW_TRACES_EXPORTER) + """Configure OTel exporter and propagators""" + environ.setdefault(OTEL_TRACES_EXPORTER, DEFAULT_SW_TRACES_EXPORTER) environ_propagators = environ.get( OTEL_PROPAGATORS, @@ -45,8 +46,7 @@ def _configure(self, **kwargs): 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( - environ.get(OTEL_TRACES_SAMPLER), + logger.debug("Configured SolarWindsDistro: {}, {}".format( environ.get(OTEL_TRACES_EXPORTER), environ.get(OTEL_PROPAGATORS) )) diff --git a/opentelemetry_distro_solarwinds/exporter.py b/opentelemetry_distro_solarwinds/exporter.py index ce23637e..3a03355e 100644 --- a/opentelemetry_distro_solarwinds/exporter.py +++ b/opentelemetry_distro_solarwinds/exporter.py @@ -5,26 +5,25 @@ """ import logging -import os from opentelemetry.sdk.trace.export import SpanExporter -from opentelemetry_distro_solarwinds.extension.oboe import (Context, Metadata, - Reporter) +from opentelemetry_distro_solarwinds.extension.oboe import ( + Context, + Metadata +) from opentelemetry_distro_solarwinds.w3c_transformer import W3CTransformer logger = logging.getLogger(__file__) class SolarWindsSpanExporter(SpanExporter): - """SolarWinds span exporter. - - Reports instrumentation data to the SolarWinds backend. + """SolarWinds custom span exporter for the SolarWinds backend. + Initialization requires a liboboe reporter. """ - def __init__(self, *args, **kw_args): + def __init__(self, reporter, *args, **kw_args): super().__init__(*args, **kw_args) - self.reporter = None - self._initialize_solarwinds_reporter() + self.reporter = reporter def export(self, spans): """Export to AO events and report via liboboe. @@ -88,38 +87,6 @@ def _report_info_event(self, event): evt.addInfo(k, v) self.reporter.sendReport(evt, False) - def _initialize_solarwinds_reporter(self): - """Initialize liboboe.""" - log_level = os.environ.get('SOLARWINDS_DEBUG_LEVEL', 3) - try: - log_level = int(log_level) - except ValueError: - log_level = 3 - self.reporter = Reporter( - hostname_alias='', - log_level=log_level, - log_file_path='', - max_transactions=-1, - max_flush_wait_time=-1, - events_flush_interval=-1, - max_request_size_bytes=-1, - reporter='ssl', - host=os.environ.get('SOLARWINDS_COLLECTOR', ''), - service_key=os.environ.get('SOLARWINDS_SERVICE_KEY', ''), - trusted_path='', - buffer_size=-1, - trace_metrics=-1, - histogram_precision=-1, - token_bucket_capacity=-1, - token_bucket_rate=-1, - file_single=0, - ec2_metadata_timeout=1000, - grpc_proxy='', - stdout_clear_nonblocking=0, - is_grpc_clean_hack_enabled=False, - w3c_trace_format=1, - ) - @staticmethod def _build_metadata(span_context): return Metadata.fromString(