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

adding support for Jaeger propagator #1219

Merged
merged 19 commits into from
Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
2 changes: 2 additions & 0 deletions opentelemetry-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
([#1373](https://github.com/open-telemetry/opentelemetry-python/pull/1373))
- Rename Meter class to Accumulator in Metrics SDK
([#1372](https://github.com/open-telemetry/opentelemetry-python/pull/1372))
- Added support for Jaeger propagator
([#1219](https://github.com/open-telemetry/opentelemetry-python/pull/1219))

## Version 0.15b0

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import typing
import urllib.parse

import opentelemetry.trace as trace
from opentelemetry import baggage
from opentelemetry.context import Context, get_current
from opentelemetry.trace.propagation.textmap import (
Getter,
Setter,
TextMapPropagator,
TextMapPropagatorT,
)


class JaegerPropagator(TextMapPropagator):
"""Propagator for the Jaeger format.

See: https://www.jaegertracing.io/docs/1.19/client-libraries/#propagation-format
"""

TRACE_ID_KEY = "uber-trace-id"
BAGGAGE_PREFIX = "uberctx-"

def extract(
self,
getter: Getter[TextMapPropagatorT],
carrier: TextMapPropagatorT,
context: typing.Optional[Context] = None,
) -> Context:

if context is None:
context = get_current()
fields = _extract_first_element(
getter.get(carrier, self.TRACE_ID_KEY)
).split(":")

context = self._extract_baggage(getter, carrier, context)
if len(fields) != 4:
return trace.set_span_in_context(trace.INVALID_SPAN, context)

trace_id, span_id, _parent_id, flags = fields
if (
trace_id == trace.INVALID_TRACE_ID
or span_id == trace.INVALID_SPAN_ID
):
return trace.set_span_in_context(trace.INVALID_SPAN, context)

span = trace.DefaultSpan(
trace.SpanContext(
trace_id=int(trace_id, 16),
span_id=int(span_id, 16),
is_remote=True,
trace_flags=trace.TraceFlags(
int(flags, 16) & trace.TraceFlags.SAMPLED
),
)
)
return trace.set_span_in_context(span, context)

def inject(
self,
set_in_carrier: Setter[TextMapPropagatorT],
carrier: TextMapPropagatorT,
context: typing.Optional[Context] = None,
) -> None:
span = trace.get_current_span(context=context)
span_context = span.get_span_context()
if span_context == trace.INVALID_SPAN_CONTEXT:
return

span_parent_id = span.parent.span_id if span.parent else 0
trace_flags = span_context.trace_flags
# set debug flag to True if sampled flag is set
if trace_flags.sampled:
trace_flags |= 0x02
Rashmi-K-A marked this conversation as resolved.
Show resolved Hide resolved

# set span identity
set_in_carrier(
carrier,
self.TRACE_ID_KEY,
_format_uber_trace_id(
span_context.trace_id,
span_context.span_id,
span_parent_id,
trace_flags,
),
)

# set span baggage, if any
baggage_entries = baggage.get_all(context=context)
if not baggage_entries:
return
for key, value in baggage_entries.items():
baggage_key = self.BAGGAGE_PREFIX + key
set_in_carrier(
carrier, baggage_key, urllib.parse.quote(str(value))
)

def _extract_baggage(self, getter, carrier, context):
baggage_keys = [
key
for key in getter.keys(carrier)
if key.startswith(self.BAGGAGE_PREFIX)
]
for key in baggage_keys:
value = _extract_first_element(getter.get(carrier, key))
context = baggage.set_baggage(
key.replace(self.BAGGAGE_PREFIX, ""),
urllib.parse.unquote(value).strip(),
context=context,
)
return context


def _format_uber_trace_id(trace_id, span_id, parent_span_id, flags):
return "{:032x}:{:016x}:{:016x}:{:02x}".format(
trace_id, span_id, parent_span_id, flags
)


def _extract_first_element(
items: typing.Iterable[TextMapPropagatorT],
) -> typing.Optional[TextMapPropagatorT]:
if items is None:
return None
return next(iter(items), None)
145 changes: 145 additions & 0 deletions opentelemetry-sdk/tests/trace/propagation/test_jaeger_propagator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest

import opentelemetry.sdk.trace as trace
import opentelemetry.sdk.trace.propagation.jaeger_propagator as jaeger
import opentelemetry.trace as trace_api
from opentelemetry import baggage
from opentelemetry.trace.propagation.textmap import DictGetter

FORMAT = jaeger.JaegerPropagator()


carrier_getter = DictGetter()


def get_context_new_carrier(old_carrier, carrier_baggage=None):

ctx = FORMAT.extract(carrier_getter, old_carrier)
if carrier_baggage:
for key, value in carrier_baggage.items():
ctx = baggage.set_baggage(key, value, ctx)
parent_span_context = trace_api.get_current_span(ctx).get_span_context()

parent = trace._Span("parent", parent_span_context)
child = trace._Span(
"child",
trace_api.SpanContext(
parent_span_context.trace_id,
trace_api.RandomIdsGenerator().generate_span_id(),
is_remote=False,
trace_flags=parent_span_context.trace_flags,
trace_state=parent_span_context.trace_state,
),
parent=parent.get_span_context(),
)

new_carrier = {}
ctx = trace_api.set_span_in_context(child, ctx)

FORMAT.inject(dict.__setitem__, new_carrier, context=ctx)

return ctx, new_carrier


class TestJaegerPropagator(unittest.TestCase):
@classmethod
def setUpClass(cls):
ids_generator = trace_api.RandomIdsGenerator()
cls.trace_id = ids_generator.generate_trace_id()
cls.span_id = ids_generator.generate_span_id()
cls.parent_span_id = ids_generator.generate_span_id()
cls.serialized_uber_trace_id = "{:032x}:{:016x}:{:016x}:{:02x}".format(
cls.trace_id, cls.span_id, cls.parent_span_id, 1
)

def test_extract_valid_span(self):
old_carrier = {FORMAT.TRACE_ID_KEY: self.serialized_uber_trace_id}
ctx = FORMAT.extract(carrier_getter, old_carrier)
span_context = trace_api.get_current_span(ctx).get_span_context()
self.assertEqual(span_context.trace_id, self.trace_id)
self.assertEqual(span_context.span_id, self.span_id)

def test_trace_id(self):
old_carrier = {FORMAT.TRACE_ID_KEY: self.serialized_uber_trace_id}
_, new_carrier = get_context_new_carrier(old_carrier)
self.assertEqual(
self.serialized_uber_trace_id.split(":")[0],
new_carrier[FORMAT.TRACE_ID_KEY].split(":")[0],
)

def test_parent_span_id(self):
old_carrier = {FORMAT.TRACE_ID_KEY: self.serialized_uber_trace_id}
_, new_carrier = get_context_new_carrier(old_carrier)
span_id = self.serialized_uber_trace_id.split(":")[1]
parent_span_id = new_carrier[FORMAT.TRACE_ID_KEY].split(":")[2]
self.assertEqual(span_id, parent_span_id)

def test_sampled_flag(self):
old_carrier = {FORMAT.TRACE_ID_KEY: self.serialized_uber_trace_id}
_, new_carrier = get_context_new_carrier(old_carrier)
sample_flag_value = (
int(new_carrier[FORMAT.TRACE_ID_KEY].split(":")[3]) & 0x01
)
self.assertEqual(1, sample_flag_value)

def test_baggage(self):
old_carrier = {FORMAT.TRACE_ID_KEY: self.serialized_uber_trace_id}
input_baggage = {"key1": "value1"}
_, new_carrier = get_context_new_carrier(old_carrier, input_baggage)
ctx = FORMAT.extract(carrier_getter, new_carrier)
self.assertDictEqual(input_baggage, ctx["baggage"])

def test_non_string_baggage(self):
old_carrier = {FORMAT.TRACE_ID_KEY: self.serialized_uber_trace_id}
input_baggage = {"key1": 1, "key2": True}
formatted_baggage = {"key1": "1", "key2": "True"}
_, new_carrier = get_context_new_carrier(old_carrier, input_baggage)
ctx = FORMAT.extract(carrier_getter, new_carrier)
self.assertDictEqual(formatted_baggage, ctx["baggage"])

def test_extract_invalid_uber_trace_id(self):
old_carrier = {
"uber-trace-id": "000000000000000000000000deadbeef:00000000deadbef0:00",
"uberctx-key1": "value1",
}
formatted_baggage = {"key1": "value1"}
context = FORMAT.extract(carrier_getter, old_carrier)
span_context = trace_api.get_current_span(context).get_span_context()
self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID)
self.assertDictEqual(formatted_baggage, context["baggage"])

def test_extract_invalid_trace_id(self):
old_carrier = {
"uber-trace-id": "00000000000000000000000000000000:00000000deadbef0:00:00",
"uberctx-key1": "value1",
}
formatted_baggage = {"key1": "value1"}
context = FORMAT.extract(carrier_getter, old_carrier)
span_context = trace_api.get_current_span(context).get_span_context()
self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID)
self.assertDictEqual(formatted_baggage, context["baggage"])

def test_extract_invalid_span_id(self):
old_carrier = {
"uber-trace-id": "000000000000000000000000deadbeef:0000000000000000:00:00",
"uberctx-key1": "value1",
}
formatted_baggage = {"key1": "value1"}
context = FORMAT.extract(carrier_getter, old_carrier)
span_context = trace_api.get_current_span(context).get_span_context()
self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID)
self.assertDictEqual(formatted_baggage, context["baggage"])