Skip to content
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-2313 Add basic TraceState handling and W3C trace context propagation #11

Merged
merged 78 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
23e4f8e
Add stub for SolarWindsFormat custom propagator
tammy-baylis-swi Feb 25, 2022
b591d4f
(WIP) tracestate sometimes gets updated with span_id
tammy-baylis-swi Feb 26, 2022
9ceb4df
Add helpers to format span_id, trace_flags
tammy-baylis-swi Feb 28, 2022
a5701a1
Add temp manual trace_state update logic
tammy-baylis-swi Feb 28, 2022
a6533bc
Rm SolarWindsFormat.extract duplicate code from TraceContextTextMapPr…
tammy-baylis-swi Mar 1, 2022
05b8887
Fix propagator use of tracestate.update
tammy-baylis-swi Mar 8, 2022
a7c6244
Rename to SwSampler, WIP decision-tracestate-attributes management
tammy-baylis-swi Mar 8, 2022
d91e276
WIP attributes
tammy-baylis-swi Mar 9, 2022
ae29003
Rm unnecessary attributes touching by Propagator
tammy-baylis-swi Mar 9, 2022
394ae52
SwSampler adds/updates sw.w3c.tracestate, sw.tracestate_parent_id
tammy-baylis-swi Mar 9, 2022
f6ff7f4
SwSampler adds/updates sw.parent_span_id
tammy-baylis-swi Mar 9, 2022
c380527
Refactor/Break up _SwSampler.should_sample into several helpers
tammy-baylis-swi Mar 10, 2022
5659f5b
Fix format of args sent to Context::getDecisions
tammy-baylis-swi Mar 10, 2022
f17b2b8
Fix duplication of sw.parent_span_id by Sampler; stop invalid attribu…
tammy-baylis-swi Mar 11, 2022
52e655a
install_requires less strict for Django ASGI support
tammy-baylis-swi Mar 14, 2022
69a487a
Mv SolarWindsFormat to SolarWindsPropagator
tammy-baylis-swi Mar 16, 2022
cb5800e
Mv ot_ao_transformer to w3c_transformer, used by Exporter Sampler Pro…
tammy-baylis-swi Mar 16, 2022
537cf0a
Simplify variables used in Propagator.inject
tammy-baylis-swi Mar 17, 2022
88529a1
Refactor of _SwSampler
tammy-baylis-swi Mar 17, 2022
761e34f
Rm ununsed variables
tammy-baylis-swi Mar 17, 2022
07ec508
Fix trace_flags and some log formatting
tammy-baylis-swi Mar 17, 2022
77dbfb6
Rm f-strings for older Python versions
tammy-baylis-swi Mar 17, 2022
c4ca055
Mv tracestate sw value format checks to _SwSampler.calculate_liboboe_…
tammy-baylis-swi Mar 17, 2022
30899bf
Misc fixups for style and comments
tammy-baylis-swi Mar 17, 2022
7e4e8c9
Sampler lets liboboe do all the decision-making
tammy-baylis-swi Mar 18, 2022
d9c0e63
Fix flag format at trace_state and attribute update
tammy-baylis-swi Mar 21, 2022
5204536
Update Propagator debug logs for clarity
tammy-baylis-swi Mar 22, 2022
12d4cbd
Fix sw.tracestate_parent_id assignment
tammy-baylis-swi Mar 22, 2022
bd315a6
Preserve non-vendor values of injected tracestate header
tammy-baylis-swi Mar 22, 2022
8f5a215
Add TraceOptions class and an init with headers from propagator.extract
tammy-baylis-swi Mar 23, 2022
60522b7
Mv TraceOptions to XTraceOptions; add from_context and iter,str overl…
tammy-baylis-swi Mar 23, 2022
3e175ba
Sampler passes xtraceoptions from otel context to liboboe
tammy-baylis-swi Mar 24, 2022
38639c9
Sampler passes liboboe tracestate from header tracestate, not tracepa…
tammy-baylis-swi Mar 24, 2022
f95bf46
Fix XTraceOptions init when other headers provided
tammy-baylis-swi Mar 24, 2022
af11112
Add TT signature handling; Sampler gives liboboe None if parent not v…
tammy-baylis-swi Mar 28, 2022
322949b
Add SolarWindsResponsePropagator (wip); up otel-instrumentation version
tammy-baylis-swi Mar 30, 2022
ff201c7
Sampler creates xtraceoptions_response for tracestate piggyback; Resp…
tammy-baylis-swi Apr 1, 2022
05f03c7
Fix sampler tracestate creation; mv XTraceOptions str overload to to_…
tammy-baylis-swi Apr 4, 2022
3b62bc5
Sampler create x-trace-options-response if parent tracestate exists
tammy-baylis-swi Apr 4, 2022
1dfd489
Fix bug in to_options_header
tammy-baylis-swi Apr 4, 2022
99d2fc4
Add W3CTransformer classmethods
tammy-baylis-swi Apr 5, 2022
32df3d0
Fix Sampler OBOE_SETTINGS_UNSET of tracing_mode, sample_rate, trigger…
tammy-baylis-swi Apr 6, 2022
7444d43
String formatting a bit less brittle
tammy-baylis-swi Apr 6, 2022
84a863e
Fix trace_state KV add at inject
tammy-baylis-swi Apr 6, 2022
de88565
Style and comment for ResponsePropagator
tammy-baylis-swi Apr 6, 2022
ec2c317
Python 3.6+, otel SDK 1.10.0, instrumentation 0.29.b0
tammy-baylis-swi Apr 8, 2022
59b1bac
xtraceoptions log lines to debug level
tammy-baylis-swi Apr 8, 2022
45f1910
Assign several keys/attrs as constants
tammy-baylis-swi Apr 9, 2022
552398c
Fix sw.tracestate_parent_id, sw.w3c.tracestate KV/attribute creation
tammy-baylis-swi Apr 11, 2022
58acd70
Rm _LiboboeDecision class to use dict instead
tammy-baylis-swi Apr 11, 2022
783cac0
distro set_global_textmap hardcode instead of env var for OTEL_PROPAG…
tammy-baylis-swi Apr 11, 2022
b58e29b
traceparent_from_context returns lowercase
tammy-baylis-swi Apr 11, 2022
b08517e
Fixups: variable assignments, rm ununsed param
tammy-baylis-swi Apr 11, 2022
f7f487a
Fix comment
tammy-baylis-swi Apr 12, 2022
3aad13d
NH-11236 sampler creates Service Entry Internal KVs
tammy-baylis-swi Apr 14, 2022
ffccf28
SWKeys KV added to root span, sw_keys always str, trigger_trace as in…
tammy-baylis-swi Apr 14, 2022
3a90bdb
Simplify x-trace-options handling
tammy-baylis-swi Apr 18, 2022
118c1bc
Sampler always sets SWKeys KV if available; calculate_attributes refa…
tammy-baylis-swi Apr 18, 2022
23a12a2
Sampler sets bucket/sample KVs if liboboe decision not cont'd
tammy-baylis-swi Apr 18, 2022
526df98
Merge branch 'add-custom-propagator' into NH-11236-service-entry-inte…
tammy-baylis-swi Apr 18, 2022
7077e6a
Latest otel 1.11.0/0.30b0, rename entry point to solarwinds_propagato…
tammy-baylis-swi Apr 19, 2022
f9171c8
Exporter and Propagator(s) are env var configurable
tammy-baylis-swi Apr 19, 2022
0aef14c
NH-9150 Use oboe_api fns for no default timestamp
tammy-baylis-swi Apr 21, 2022
c77afc9
Merge pull request #13 from appoptics/nh-9150-use-oboe-api-no-default…
tammy-baylis-swi Apr 21, 2022
68a0e63
Merge pull request #12 from appoptics/NH-11236-service-entry-internal…
tammy-baylis-swi Apr 22, 2022
7b75e65
Separate concerns: env vars in distro, component init in configurator
tammy-baylis-swi Apr 22, 2022
600e5b2
Custom Propagator gets tracestate from carrier in case of upstream pr…
tammy-baylis-swi Apr 27, 2022
ede5686
Call TraceState.from_header correctly
tammy-baylis-swi Apr 28, 2022
2842209
NH-2313 adjust CompositePropagator config with OTEL_PROPAGATORS
tammy-baylis-swi Apr 28, 2022
7ffb1a9
solarwinds_sampler cannot be env default because not in OTel _KNOWN_S…
tammy-baylis-swi Apr 28, 2022
02bde11
Merge pull request #15 from appoptics/NH-2313-adjust-propagator-confi…
tammy-baylis-swi May 2, 2022
54a0e7d
Merge pull request #16 from appoptics/NH-2313-propagator-gets-tracest…
tammy-baylis-swi May 2, 2022
1241e7a
load_entry_point instead of iter for singulars
tammy-baylis-swi May 3, 2022
1b830a1
Merge pull request #14 from appoptics/NH-12018-distro-and-configurato…
tammy-baylis-swi May 4, 2022
73be566
Move _initialize_solarwinds_reporter from Exporter to Configurator
tammy-baylis-swi May 4, 2022
126e030
Fix non-SW sampler,exporter configure
tammy-baylis-swi May 4, 2022
01f1e98
Always use SW Sampler
tammy-baylis-swi May 5, 2022
7e758df
Merge pull request #17 from appoptics/NH-12018-oboe-reporter-in-confi…
tammy-baylis-swi May 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions opentelemetry_distro_solarwinds/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
__version__ = "0.0.1"

COMMA = ","
COMMA_W3C_SANITIZED = "...."
EQUALS = "="
EQUALS_W3C_SANITIZED = "####"
SW_TRACESTATE_KEY = "sw"
OTEL_CONTEXT_SW_OPTIONS_KEY = "sw_xtraceoptions"
OTEL_CONTEXT_SW_SIGNATURE_KEY = "sw_signature"
DEFAULT_SW_TRACES_EXPORTER = "solarwinds_exporter"
156 changes: 156 additions & 0 deletions opentelemetry_distro_solarwinds/configurator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""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 (
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__)

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):
"""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",
self._DEFAULT_SW_TRACES_SAMPLER
)()
except:
logger.exception(
"Failed to load configured sampler {}".format(
self._DEFAULT_SW_TRACES_SAMPLER
)
)
raise
trace.set_tracer_provider(
TracerProvider(sampler=sampler)
)

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)

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 = []
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_name)
).load()()
)
except Exception:
logger.exception(
"Failed to load configured propagator {}".format(
propagator_name
)
)
raise
set_global_textmap(CompositePropagator(propagators))

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,
)
60 changes: 44 additions & 16 deletions opentelemetry_distro_solarwinds/distro.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
"""Module to configure OpenTelemetry agent to work with SolarWinds backend"""
"""Module to configure OpenTelemetry to work with SolarWinds backend"""

from opentelemetry import trace
import logging
from os import environ

from opentelemetry.environment_variables import (
OTEL_PROPAGATORS,
OTEL_TRACES_EXPORTER
)
from opentelemetry.instrumentation.distro import BaseDistro
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from opentelemetry_distro_solarwinds.exporter import SolarWindsSpanExporter
from opentelemetry_distro_solarwinds.sampler import ParentBasedAoSampler
from opentelemetry_distro_solarwinds import DEFAULT_SW_TRACES_EXPORTER

logger = logging.getLogger(__name__)

class SolarWindsDistro(BaseDistro):
"""SolarWinds custom distro for OpenTelemetry agents.
"""OpenTelemetry Distro for SolarWinds reporting environment"""

_TRACECONTEXT_PROPAGATOR = "tracecontext"
_SW_PROPAGATOR = "solarwinds_propagator"
_DEFAULT_SW_PROPAGATORS = [
_TRACECONTEXT_PROPAGATOR,
"baggage",
_SW_PROPAGATOR,
]

With this custom distro, the following functionality is introduced:
- no functionality added at this time
"""
def _configure(self, **kwargs):
# automatically make use of custom SolarWinds sampler
trace.set_tracer_provider(
TracerProvider(sampler=ParentBasedAoSampler()))
# Automatically configure the SolarWinds Span exporter
span_exporter = BatchSpanProcessor(SolarWindsSpanExporter())
trace.get_tracer_provider().add_span_processor(span_exporter)
"""Configure OTel exporter and propagators"""
environ.setdefault(OTEL_TRACES_EXPORTER, DEFAULT_SW_TRACES_EXPORTER)

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:
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(
environ.get(OTEL_TRACES_EXPORTER),
environ.get(OTEL_PROPAGATORS)
))
73 changes: 21 additions & 52 deletions opentelemetry_distro_solarwinds/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.ot_ao_transformer import transform_id
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.
Expand All @@ -36,31 +35,31 @@ def export(self, spans):
md = self._build_metadata(span.get_span_context())
if span.parent and span.parent.is_valid:
# If there is a parent, we need to add an edge to this parent to this entry event
logger.debug("Continue trace from %s", md.toString())
logger.debug("Continue trace from {}".format(md.toString()))
parent_md = self._build_metadata(span.parent)
evt = Context.startTrace(md, int(span.start_time / 1000),
evt = Context.createEntry(md, int(span.start_time / 1000),
parent_md)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit and inherited code, but let's rename this to something like startSpan / spanEntry and the matching one stopSpan / spanExit or similar, just to clarify this about a span rather than entire trace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's going to be a separate liboboe PR (oboe itself or the SWIG interface only if possible?), related to NH-9150, NH-7246, and this comment: https://swicloud.atlassian.net/wiki/spaces/NIT/pages/2823618801/liboboe+and+OTel+Python+custom-distro?focusedCommentId=2826010703#comment-2826010703

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've now addressed this in 0aef14c by switching to use the new Context.createEntry functions Jerry added to oboe_api in NH-9150.

else:
# In OpenTelemrtry, there are no events with individual IDs, but only a span ID
# and trace ID. Thus, the entry event needs to be generated such that it has the
# same op ID as the span ID of the OTel span.
logger.debug("Start a new trace %s", md.toString())
evt = Context.startTrace(md, int(span.start_time / 1000))
logger.debug("Start a new trace {}".format(md.toString()))
evt = Context.createEntry(md, int(span.start_time / 1000))
evt.addInfo('Layer', span.name)
evt.addInfo('Language', 'Python')
for k, v in span.attributes.items():
evt.addInfo(k, v)
self.reporter.sendReport(evt)
self.reporter.sendReport(evt, False)

for event in span.events:
if event.name == 'exception':
self._report_exception_event(event)
else:
self._report_info_event(event)

evt = Context.stopTrace(int(span.end_time / 1000))
evt = Context.createExit(int(span.end_time / 1000))
evt.addInfo('Layer', span.name)
self.reporter.sendReport(evt)
self.reporter.sendReport(evt, False)

def _report_exception_event(self, event):
evt = Context.createEvent(int(event.timestamp / 1000))
Expand All @@ -76,7 +75,7 @@ def _report_exception_event(self, event):
if k not in ('exception.type', 'exception.message',
'exception.stacktrace'):
evt.addInfo(k, v)
self.reporter.sendReport(evt)
self.reporter.sendReport(evt, False)

def _report_info_event(self, event):
print("Found info event")
Expand All @@ -86,40 +85,10 @@ def _report_info_event(self, event):
evt.addInfo('Label', 'info')
for k, v in event.attributes.items():
evt.addInfo(k, v)
self.reporter.sendReport(evt)

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,
)
self.reporter.sendReport(evt, False)

@staticmethod
def _build_metadata(span_context):
return Metadata.fromString(transform_id(span_context))
return Metadata.fromString(
W3CTransformer.traceparent_from_context(span_context)
)
17 changes: 0 additions & 17 deletions opentelemetry_distro_solarwinds/ot_ao_transformer.py

This file was deleted.

Loading