Skip to content

Commit e4d8949

Browse files
johananlcarlosalberto
authored andcommitted
OpenTracing Bridge - Initial implementation (#211)
Initial implementation, without baggage support.
1 parent 1fd8659 commit e4d8949

File tree

13 files changed

+941
-8
lines changed

13 files changed

+941
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
OpenTracing Shim for OpenTelemetry
2+
============================================================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-opentracing-shim.svg
7+
:target: https://pypi.org/project/opentelemetry-opentracing-shim/
8+
9+
Installation
10+
------------
11+
12+
::
13+
14+
pip install opentelemetry-opentracing-shim
15+
16+
References
17+
----------
18+
19+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2019, 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-opentracing-shim
17+
description = OpenTracing Shim 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/ext/opentelemetry-ext-opentracing-shim
23+
platforms = any
24+
license = Apache-2.0
25+
classifiers =
26+
Development Status :: 3 - Alpha
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.4
32+
Programming Language :: Python :: 3.5
33+
Programming Language :: Python :: 3.6
34+
Programming Language :: Python :: 3.7
35+
36+
[options]
37+
python_requires = >=3.4
38+
package_dir=
39+
=src
40+
packages=find_namespace:
41+
install_requires =
42+
opentracing
43+
opentelemetry-api
44+
45+
[options.packages.find]
46+
where = src
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2019, 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+
import os
15+
16+
import setuptools
17+
18+
BASE_DIR = os.path.dirname(__file__)
19+
VERSION_FILENAME = os.path.join(
20+
BASE_DIR, "src", "opentelemetry", "ext", "opentracing_shim", "version.py"
21+
)
22+
PACKAGE_INFO = {}
23+
with open(VERSION_FILENAME) as f:
24+
exec(f.read(), PACKAGE_INFO)
25+
26+
setuptools.setup(version=PACKAGE_INFO["__version__"])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Copyright 2019, 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 logging
16+
17+
import opentracing
18+
19+
from opentelemetry.ext.opentracing_shim import util
20+
21+
logger = logging.getLogger(__name__)
22+
23+
24+
def create_tracer(otel_tracer):
25+
return TracerShim(otel_tracer)
26+
27+
28+
class SpanContextShim(opentracing.SpanContext):
29+
def __init__(self, otel_context):
30+
self._otel_context = otel_context
31+
32+
def unwrap(self):
33+
"""Returns the wrapped OpenTelemetry `SpanContext` object."""
34+
35+
return self._otel_context
36+
37+
@property
38+
def baggage(self):
39+
logger.warning(
40+
"Using unimplemented property baggage on class %s.",
41+
self.__class__.__name__,
42+
)
43+
# TODO: Implement.
44+
45+
46+
class SpanShim(opentracing.Span):
47+
def __init__(self, tracer, context, span):
48+
super().__init__(tracer, context)
49+
self._otel_span = span
50+
51+
def unwrap(self):
52+
"""Returns the wrapped OpenTelemetry `Span` object."""
53+
54+
return self._otel_span
55+
56+
def set_operation_name(self, operation_name):
57+
self._otel_span.update_name(operation_name)
58+
return self
59+
60+
def finish(self, finish_time=None):
61+
end_time = finish_time
62+
if end_time is not None:
63+
end_time = util.time_seconds_to_ns(finish_time)
64+
self._otel_span.end(end_time=end_time)
65+
66+
def set_tag(self, key, value):
67+
self._otel_span.set_attribute(key, value)
68+
return self
69+
70+
def log_kv(self, key_values, timestamp=None):
71+
if timestamp is not None:
72+
event_timestamp = util.time_seconds_to_ns(timestamp)
73+
else:
74+
event_timestamp = None
75+
76+
event_name = util.event_name_from_kv(key_values)
77+
self._otel_span.add_event(event_name, event_timestamp, key_values)
78+
return self
79+
80+
def set_baggage_item(self, key, value):
81+
logger.warning(
82+
"Calling unimplemented method set_baggage_item() on class %s",
83+
self.__class__.__name__,
84+
)
85+
# TODO: Implement.
86+
87+
def get_baggage_item(self, key):
88+
logger.warning(
89+
"Calling unimplemented method get_baggage_item() on class %s",
90+
self.__class__.__name__,
91+
)
92+
# TODO: Implement.
93+
94+
# TODO: Verify calls to deprecated methods `log_event()` and `log()` on
95+
# base class work properly (it's probably fine because both methods call
96+
# `log_kv()`).
97+
98+
99+
class ScopeShim(opentracing.Scope):
100+
"""A `ScopeShim` wraps the OpenTelemetry functionality related to span
101+
activation/deactivation while using OpenTracing `Scope` objects for
102+
presentation.
103+
104+
There are two ways to construct a `ScopeShim` object: using the default
105+
initializer and using the `from_context_manager()` class method.
106+
107+
It is necessary to have both ways for constructing `ScopeShim` objects
108+
because in some cases we need to create the object from a context manager,
109+
in which case our only way of retrieving a `Span` object is by calling the
110+
`__enter__()` method on the context manager, which makes the span active in
111+
the OpenTelemetry tracer; whereas in other cases we need to accept a
112+
`SpanShim` object and wrap it in a `ScopeShim`.
113+
"""
114+
115+
def __init__(self, manager, span, span_cm=None):
116+
super().__init__(manager, span)
117+
self._span_cm = span_cm
118+
119+
# TODO: Change type of `manager` argument to `opentracing.ScopeManager`? We
120+
# need to get rid of `manager.tracer` for this.
121+
@classmethod
122+
def from_context_manager(cls, manager, span_cm):
123+
"""Constructs a `ScopeShim` from an OpenTelemetry `Span` context
124+
manager (as returned by `Tracer.use_span()`).
125+
"""
126+
127+
otel_span = span_cm.__enter__()
128+
span_context = SpanContextShim(otel_span.get_context())
129+
span = SpanShim(manager.tracer, span_context, otel_span)
130+
return cls(manager, span, span_cm)
131+
132+
def close(self):
133+
if self._span_cm is not None:
134+
# We don't have error information to pass to `__exit__()` so we
135+
# pass `None` in all arguments. If the OpenTelemetry tracer
136+
# implementation requires this information, the `__exit__()` method
137+
# on `opentracing.Scope` should be overridden and modified to pass
138+
# the relevant values to this `close()` method.
139+
self._span_cm.__exit__(None, None, None)
140+
else:
141+
self._span.unwrap().end()
142+
143+
144+
class ScopeManagerShim(opentracing.ScopeManager):
145+
def __init__(self, tracer):
146+
# The only thing the `__init__()` method on the base class does is
147+
# initialize `self._noop_span` and `self._noop_scope` with no-op
148+
# objects. Therefore, it doesn't seem useful to call it.
149+
# pylint: disable=super-init-not-called
150+
self._tracer = tracer
151+
152+
def activate(self, span, finish_on_close):
153+
span_cm = self._tracer.unwrap().use_span(
154+
span.unwrap(), end_on_exit=finish_on_close
155+
)
156+
return ScopeShim.from_context_manager(self, span_cm=span_cm)
157+
158+
@property
159+
def active(self):
160+
span = self._tracer.unwrap().get_current_span()
161+
if span is None:
162+
return None
163+
164+
span_context = SpanContextShim(span.get_context())
165+
wrapped_span = SpanShim(self._tracer, span_context, span)
166+
return ScopeShim(self, span=wrapped_span)
167+
# TODO: The returned `ScopeShim` instance here always ends the
168+
# corresponding span, regardless of the `finish_on_close` value used
169+
# when activating the span. This is because here we return a *new*
170+
# `ScopeShim` rather than returning a saved instance of `ScopeShim`.
171+
# https://github.com/open-telemetry/opentelemetry-python/pull/211/files#r335398792
172+
173+
@property
174+
def tracer(self):
175+
return self._tracer
176+
177+
178+
class TracerShim(opentracing.Tracer):
179+
def __init__(self, tracer):
180+
super().__init__(scope_manager=ScopeManagerShim(self))
181+
self._otel_tracer = tracer
182+
183+
def unwrap(self):
184+
"""Returns the wrapped OpenTelemetry `Tracer` object."""
185+
186+
return self._otel_tracer
187+
188+
def start_active_span(
189+
self,
190+
operation_name,
191+
child_of=None,
192+
references=None,
193+
tags=None,
194+
start_time=None,
195+
ignore_active_span=False,
196+
finish_on_close=True,
197+
):
198+
span = self.start_span(
199+
operation_name=operation_name,
200+
child_of=child_of,
201+
references=references,
202+
tags=tags,
203+
start_time=start_time,
204+
ignore_active_span=ignore_active_span,
205+
)
206+
return self._scope_manager.activate(span, finish_on_close)
207+
208+
def start_span(
209+
self,
210+
operation_name=None,
211+
child_of=None,
212+
references=None,
213+
tags=None,
214+
start_time=None,
215+
ignore_active_span=False,
216+
):
217+
# Use active span as parent when no explicit parent is specified.
218+
if not ignore_active_span and not child_of:
219+
child_of = self.active_span
220+
221+
# Use the specified parent or the active span if possible. Otherwise,
222+
# use a `None` parent, which triggers the creation of a new trace.
223+
parent = child_of.unwrap() if child_of else None
224+
span = self._otel_tracer.create_span(operation_name, parent)
225+
226+
if references:
227+
for ref in references:
228+
span.add_link(ref.referenced_context.unwrap())
229+
230+
if tags:
231+
for key, value in tags.items():
232+
span.set_attribute(key, value)
233+
234+
# The OpenTracing API expects time values to be `float` values which
235+
# represent the number of seconds since the epoch. OpenTelemetry
236+
# represents time values as nanoseconds since the epoch.
237+
start_time_ns = start_time
238+
if start_time_ns is not None:
239+
start_time_ns = util.time_seconds_to_ns(start_time)
240+
241+
span.start(start_time=start_time_ns)
242+
context = SpanContextShim(span.get_context())
243+
return SpanShim(self, context, span)
244+
245+
def inject(self, span_context, format, carrier):
246+
# pylint: disable=redefined-builtin
247+
logger.warning(
248+
"Calling unimplemented method inject() on class %s",
249+
self.__class__.__name__,
250+
)
251+
# TODO: Implement.
252+
253+
def extract(self, format, carrier):
254+
# pylint: disable=redefined-builtin
255+
logger.warning(
256+
"Calling unimplemented method extract() on class %s",
257+
self.__class__.__name__,
258+
)
259+
# TODO: Implement.

0 commit comments

Comments
 (0)