-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NH-12018 Separate Distro vs Configurator #14
Changes from all commits
7b75e65
2842209
7ffb1a9
02bde11
1241e7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about the propagators configuration here (I may not have fully read/understood your PR description or the Otel agent's design): I don't see a new propagator written by us here, so why can't the Otel agent do the configuration by itself? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah, I think I should document this all in one place instead of one PR description and one Jira comment 😅 Our new SW propagator is either named by default or must be named by When a customer's service has NH Python installed and is launched with auto-instrumentation, this OTel Python Does that make sense and justify this code block? 🙂 Or more clarification required? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! 😺 I've documented this for future reference here: https://swicloud.atlassian.net/wiki/spaces/NIT/pages/2867726621/NH+Python+Troubleshooting#%E2%80%9CHow-does-NH-Python-code-get-run%3F%E2%80%9D |
||
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()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) | ||
logger.debug("Configured SolarWindsDistro: {}, {}, {}".format( | ||
environ.get(OTEL_TRACES_SAMPLER), | ||
environ.get(OTEL_TRACES_EXPORTER), | ||
environ.get(OTEL_PROPAGATORS) | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the
exporter
andsampler
above, what happens if there are multiple configured -- e..g if there were somehow two exporters configured, it seems like only the first result fromnext
would be taken and be the one set into the BatchSpanProcesser (and similarly with sampler and TracerProvider).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think you're right. The customer could end up somehow configuring multiples of the same name (theirs or SW exporter/sampler). Without going too far down the rabbit hole, I think the chances of this are small but non-zero if the customer is doing some funny business with their service's
sys.path
and/or the effective pkg_resources WorkingSet.In 1241e7a I've switched the two
iter_entry_points
toload_entry_point
calls, to avoid that unintended behaviour.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍