Skip to content

Commit e24ff67

Browse files
committed
Add OpenTracing propagator
Fixes #294
1 parent fcc260c commit e24ff67

File tree

9 files changed

+707
-1
lines changed

9 files changed

+707
-1
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased](https://github.com/open-telemetry/opentelemetry-python-contrib/compare/v0.17b0...HEAD)
88

9+
- `opentelemetry-propagator-opentracing` Add OpenTracing Propagator
10+
([#302](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/302))
11+
912
## [0.17b0](https://github.com/open-telemetry/opentelemetry-python-contrib/releases/tag/v0.17b0) - 2021-01-20
1013

1114
### Added
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
OpenTelemetry OpenTracing Propagator
2+
====================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-propagator-opentracing.svg
7+
:target: https://pypi.org/project/opentelemetry-propagator-opentracing/
8+
9+
Installation
10+
------------
11+
12+
::
13+
14+
pip install opentelemetry-propagator-opentracing
15+
16+
.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
17+
18+
OpenTracing Format
19+
------------------
20+
21+
===================== ======================================================================================================================================= =====================
22+
Header Name Description Required
23+
===================== ======================================================================================================================================= =====================
24+
``ot-tracer-traceid`` uint64 encoded as a string of 16 hex characters yes
25+
``ot-tracer-spanid`` uint64 encoded as a string of 16 hex characters yes
26+
``ot-tracer-sampled`` boolean encoded as a string with the values ``true`` or ``false`` no
27+
``ot-baggage-*`` repeated string to string key-value baggage items; keys are prefixed with ``ot-baggage-`` and the corresponding value is the raw string if baggage is present
28+
===================== ======================================================================================================================================= =====================
29+
30+
Interop and trace ids
31+
---------------------
32+
33+
The OpenTracing propagation format expects trace ids to be 64-bits. In order to
34+
interop with OpenTelemetry, trace ids need to be truncated to 64-bits before
35+
sending them on the wire. When truncating, the least significant (right-most)
36+
bits MUST be retained. For example, a trace id of
37+
``3c3039f4d78d5c02ee8e3e41b17ce105`` would be truncated to
38+
``ee8e3e41b17ce105``.
39+
40+
References
41+
----------
42+
43+
* `OpenTracing Project <https://opentracing.io/>`_
44+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
[metadata]
16+
name = opentelemetry-propagator-opentracing
17+
description = OpenTracing Propagator for OpenTelemetry
18+
long_description = file: README.rst
19+
long_description_content_type = text/x-rst
20+
author = OpenTelemetry Authors
21+
author_email = [email protected]
22+
url = https://github.com/open-telemetry/opentelemetry-python-contrib/propagator/opentelemetry-propagator-opentracing
23+
platforms = any
24+
license = Apache-2.0
25+
classifiers =
26+
Development Status :: 4 - Beta
27+
Intended Audience :: Developers
28+
License :: OSI Approved :: Apache Software License
29+
Programming Language :: Python
30+
Programming Language :: Python :: 3
31+
Programming Language :: Python :: 3.5
32+
Programming Language :: Python :: 3.6
33+
Programming Language :: Python :: 3.7
34+
Programming Language :: Python :: 3.8
35+
36+
[options]
37+
python_requires = >=3.5
38+
package_dir=
39+
=src
40+
packages=find_namespace:
41+
install_requires =
42+
opentelemetry-api == 0.18.dev0
43+
opentelemetry-sdk == 0.18.dev0
44+
45+
[options.packages.find]
46+
where = src
47+
48+
[options.extras_require]
49+
test =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
17+
import setuptools
18+
19+
BASE_DIR = os.path.dirname(__file__)
20+
VERSION_FILENAME = os.path.join(
21+
BASE_DIR, "src", "opentelemetry", "propagator", "opentracing", "version.py"
22+
)
23+
PACKAGE_INFO = {}
24+
with open(VERSION_FILENAME) as f:
25+
exec(f.read(), PACKAGE_INFO)
26+
27+
setuptools.setup(version=PACKAGE_INFO["__version__"])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from re import compile as re_compile
16+
from typing import Iterable, Optional
17+
18+
from opentelemetry.baggage import get_all, set_baggage
19+
from opentelemetry.context import Context
20+
from opentelemetry.trace import (
21+
INVALID_SPAN_ID,
22+
INVALID_TRACE_ID,
23+
DefaultSpan,
24+
SpanContext,
25+
TraceFlags,
26+
get_current_span,
27+
set_span_in_context,
28+
)
29+
from opentelemetry.trace.propagation.textmap import (
30+
Getter,
31+
Setter,
32+
TextMapPropagator,
33+
TextMapPropagatorT,
34+
)
35+
36+
OT_TRACE_ID_HEADER = "ot-tracer-traceid"
37+
OT_SPAN_ID_HEADER = "ot-tracer-spanid"
38+
OT_SAMPLED_HEADER = "ot-tracer-sampled"
39+
OT_BAGGAGE_PREFIX = "ot-baggage-"
40+
41+
_valid_header_name = re_compile(r"^[\w_^`!#$%&'*+.|~]+$")
42+
_valid_header_value = re_compile(r"^[\t\x20-\x7e\x80-\xff]+$")
43+
_valid_traceid = re_compile(r"[0-9a-f]{32}|[0-9a-f]{16}")
44+
_valid_spanid = re_compile(r"[0-9a-f]{16}")
45+
46+
47+
class OpenTracingPropagator(TextMapPropagator):
48+
"""Propagator for the OpenTracing HTTP header format"""
49+
50+
def extract(
51+
self,
52+
getter: Getter[TextMapPropagatorT],
53+
carrier: TextMapPropagatorT,
54+
context: Optional[Context] = None,
55+
) -> Context:
56+
57+
traceid = extract_first_element(
58+
getter.get(carrier, OT_TRACE_ID_HEADER)
59+
)
60+
61+
spanid = extract_first_element(getter.get(carrier, OT_SPAN_ID_HEADER))
62+
63+
sampled = extract_first_element(getter.get(carrier, OT_SAMPLED_HEADER))
64+
65+
if sampled == "true":
66+
traceflags = TraceFlags.SAMPLED
67+
else:
68+
traceflags = TraceFlags.DEFAULT
69+
70+
if (
71+
traceid != INVALID_TRACE_ID
72+
and _valid_traceid.match(traceid) is not None
73+
and spanid != INVALID_SPAN_ID
74+
and _valid_spanid.match(spanid) is not None
75+
):
76+
# FIXME this padding is lost when trace_id is casted to int when a
77+
# SpanContext is created. This causes a test case to fail. The
78+
# approach coded here attempts to closely follow JS implementation:
79+
# https://github.com/open-telemetry/opentelemetry-js-contrib/pull/318/files?file-filters%5B%5D=.js&file-filters%5B%5D=.json&file-filters%5B%5D=.ts&file-filters%5B%5D=dotfile#diff-824ceb4ccf0b4daec4207237a16f2ff4833d941e2a931bd45788f9620b4e8837R91
80+
if len(traceid) == 16:
81+
traceid = "".join(["0" * 16, traceid])
82+
83+
context = set_span_in_context(
84+
DefaultSpan(
85+
SpanContext(
86+
trace_id=int(traceid, 16),
87+
span_id=int(spanid, 16),
88+
is_remote=True,
89+
trace_flags=traceflags,
90+
)
91+
),
92+
context,
93+
)
94+
95+
baggage = get_all(context) or {}
96+
97+
for key in getter.keys(carrier):
98+
99+
if not key.startswith(OT_BAGGAGE_PREFIX):
100+
continue
101+
102+
baggage[key[len(OT_BAGGAGE_PREFIX) :]] = extract_first_element(
103+
getter.get(carrier, key)
104+
)
105+
106+
for key, value in baggage.items():
107+
context = set_baggage(key, value, context)
108+
109+
return context
110+
111+
def inject(
112+
self,
113+
set_in_carrier: Setter[TextMapPropagatorT],
114+
carrier: TextMapPropagatorT,
115+
context: Optional[Context] = None,
116+
) -> None:
117+
118+
span_context = get_current_span(context).get_span_context()
119+
120+
if (
121+
span_context.trace_id == INVALID_TRACE_ID
122+
or _valid_traceid.match(hex(span_context.trace_id)[2:]) is None
123+
or span_context.span_id == INVALID_SPAN_ID
124+
or _valid_spanid.match(hex(span_context.span_id)[2:]) is None
125+
):
126+
return
127+
128+
set_in_carrier(
129+
carrier, OT_TRACE_ID_HEADER, hex(span_context.trace_id)[18:]
130+
)
131+
set_in_carrier(
132+
carrier, OT_SPAN_ID_HEADER, hex(span_context.span_id)[2:],
133+
)
134+
135+
if span_context.trace_flags == TraceFlags.SAMPLED:
136+
traceflags = "true"
137+
else:
138+
traceflags = "false"
139+
140+
set_in_carrier(carrier, OT_SAMPLED_HEADER, traceflags)
141+
142+
baggage = get_all(context)
143+
144+
if not baggage:
145+
return
146+
147+
for header_name, header_value in baggage.items():
148+
149+
if (
150+
_valid_header_name.match(header_name) is None
151+
or _valid_header_value.match(header_value) is None
152+
):
153+
continue
154+
155+
set_in_carrier(
156+
carrier,
157+
"".join([OT_BAGGAGE_PREFIX, header_name]),
158+
header_value,
159+
)
160+
161+
@property
162+
def fields(self):
163+
"""Returns a set with the fields set in `inject`.
164+
165+
See
166+
`opentelemetry.trace.propagation.textmap.TextMapPropagator.fields`
167+
"""
168+
return {
169+
OT_TRACE_ID_HEADER,
170+
OT_SPAN_ID_HEADER,
171+
OT_SAMPLED_HEADER,
172+
}
173+
174+
175+
def extract_first_element(
176+
items: Iterable[TextMapPropagatorT],
177+
) -> Optional[TextMapPropagatorT]:
178+
if items is None:
179+
return None
180+
return next(iter(items), None)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
__version__ = "0.18.dev0"

propagator/opentelemetry-propagator-opentracing/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)