Skip to content

Commit

Permalink
OpenTracing Bridge - Initial implementation (#211)
Browse files Browse the repository at this point in the history
Initial implementation, without baggage support.
  • Loading branch information
johananl authored and carlosalberto committed Oct 24, 2019
1 parent 1fd8659 commit e4d8949
Show file tree
Hide file tree
Showing 13 changed files with 941 additions and 8 deletions.
19 changes: 19 additions & 0 deletions ext/opentelemetry-ext-opentracing-shim/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
OpenTracing Shim for OpenTelemetry
============================================================================

|pypi|

.. |pypi| image:: https://badge.fury.io/py/opentelemetry-opentracing-shim.svg
:target: https://pypi.org/project/opentelemetry-opentracing-shim/

Installation
------------

::

pip install opentelemetry-opentracing-shim

References
----------

* `OpenTelemetry Project <https://opentelemetry.io/>`_
46 changes: 46 additions & 0 deletions ext/opentelemetry-ext-opentracing-shim/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2019, 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.
#
[metadata]
name = opentelemetry-opentracing-shim
description = OpenTracing Shim for OpenTelemetry
long_description = file: README.rst
long_description_content_type = text/x-rst
author = OpenTelemetry Authors
author_email = cncf-opentelemetry-contributors@lists.cncf.io
url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-opentracing-shim
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 3 - Alpha
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7

[options]
python_requires = >=3.4
package_dir=
=src
packages=find_namespace:
install_requires =
opentracing
opentelemetry-api

[options.packages.find]
where = src
26 changes: 26 additions & 0 deletions ext/opentelemetry-ext-opentracing-shim/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2019, 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 os

import setuptools

BASE_DIR = os.path.dirname(__file__)
VERSION_FILENAME = os.path.join(
BASE_DIR, "src", "opentelemetry", "ext", "opentracing_shim", "version.py"
)
PACKAGE_INFO = {}
with open(VERSION_FILENAME) as f:
exec(f.read(), PACKAGE_INFO)

setuptools.setup(version=PACKAGE_INFO["__version__"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
# Copyright 2019, 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 logging

import opentracing

from opentelemetry.ext.opentracing_shim import util

logger = logging.getLogger(__name__)


def create_tracer(otel_tracer):
return TracerShim(otel_tracer)


class SpanContextShim(opentracing.SpanContext):
def __init__(self, otel_context):
self._otel_context = otel_context

def unwrap(self):
"""Returns the wrapped OpenTelemetry `SpanContext` object."""

return self._otel_context

@property
def baggage(self):
logger.warning(
"Using unimplemented property baggage on class %s.",
self.__class__.__name__,
)
# TODO: Implement.


class SpanShim(opentracing.Span):
def __init__(self, tracer, context, span):
super().__init__(tracer, context)
self._otel_span = span

def unwrap(self):
"""Returns the wrapped OpenTelemetry `Span` object."""

return self._otel_span

def set_operation_name(self, operation_name):
self._otel_span.update_name(operation_name)
return self

def finish(self, finish_time=None):
end_time = finish_time
if end_time is not None:
end_time = util.time_seconds_to_ns(finish_time)
self._otel_span.end(end_time=end_time)

def set_tag(self, key, value):
self._otel_span.set_attribute(key, value)
return self

def log_kv(self, key_values, timestamp=None):
if timestamp is not None:
event_timestamp = util.time_seconds_to_ns(timestamp)
else:
event_timestamp = None

event_name = util.event_name_from_kv(key_values)
self._otel_span.add_event(event_name, event_timestamp, key_values)
return self

def set_baggage_item(self, key, value):
logger.warning(
"Calling unimplemented method set_baggage_item() on class %s",
self.__class__.__name__,
)
# TODO: Implement.

def get_baggage_item(self, key):
logger.warning(
"Calling unimplemented method get_baggage_item() on class %s",
self.__class__.__name__,
)
# TODO: Implement.

# TODO: Verify calls to deprecated methods `log_event()` and `log()` on
# base class work properly (it's probably fine because both methods call
# `log_kv()`).


class ScopeShim(opentracing.Scope):
"""A `ScopeShim` wraps the OpenTelemetry functionality related to span
activation/deactivation while using OpenTracing `Scope` objects for
presentation.
There are two ways to construct a `ScopeShim` object: using the default
initializer and using the `from_context_manager()` class method.
It is necessary to have both ways for constructing `ScopeShim` objects
because in some cases we need to create the object from a context manager,
in which case our only way of retrieving a `Span` object is by calling the
`__enter__()` method on the context manager, which makes the span active in
the OpenTelemetry tracer; whereas in other cases we need to accept a
`SpanShim` object and wrap it in a `ScopeShim`.
"""

def __init__(self, manager, span, span_cm=None):
super().__init__(manager, span)
self._span_cm = span_cm

# TODO: Change type of `manager` argument to `opentracing.ScopeManager`? We
# need to get rid of `manager.tracer` for this.
@classmethod
def from_context_manager(cls, manager, span_cm):
"""Constructs a `ScopeShim` from an OpenTelemetry `Span` context
manager (as returned by `Tracer.use_span()`).
"""

otel_span = span_cm.__enter__()
span_context = SpanContextShim(otel_span.get_context())
span = SpanShim(manager.tracer, span_context, otel_span)
return cls(manager, span, span_cm)

def close(self):
if self._span_cm is not None:
# We don't have error information to pass to `__exit__()` so we
# pass `None` in all arguments. If the OpenTelemetry tracer
# implementation requires this information, the `__exit__()` method
# on `opentracing.Scope` should be overridden and modified to pass
# the relevant values to this `close()` method.
self._span_cm.__exit__(None, None, None)
else:
self._span.unwrap().end()


class ScopeManagerShim(opentracing.ScopeManager):
def __init__(self, tracer):
# The only thing the `__init__()` method on the base class does is
# initialize `self._noop_span` and `self._noop_scope` with no-op
# objects. Therefore, it doesn't seem useful to call it.
# pylint: disable=super-init-not-called
self._tracer = tracer

def activate(self, span, finish_on_close):
span_cm = self._tracer.unwrap().use_span(
span.unwrap(), end_on_exit=finish_on_close
)
return ScopeShim.from_context_manager(self, span_cm=span_cm)

@property
def active(self):
span = self._tracer.unwrap().get_current_span()
if span is None:
return None

span_context = SpanContextShim(span.get_context())
wrapped_span = SpanShim(self._tracer, span_context, span)
return ScopeShim(self, span=wrapped_span)
# TODO: The returned `ScopeShim` instance here always ends the
# corresponding span, regardless of the `finish_on_close` value used
# when activating the span. This is because here we return a *new*
# `ScopeShim` rather than returning a saved instance of `ScopeShim`.
# https://github.com/open-telemetry/opentelemetry-python/pull/211/files#r335398792

@property
def tracer(self):
return self._tracer


class TracerShim(opentracing.Tracer):
def __init__(self, tracer):
super().__init__(scope_manager=ScopeManagerShim(self))
self._otel_tracer = tracer

def unwrap(self):
"""Returns the wrapped OpenTelemetry `Tracer` object."""

return self._otel_tracer

def start_active_span(
self,
operation_name,
child_of=None,
references=None,
tags=None,
start_time=None,
ignore_active_span=False,
finish_on_close=True,
):
span = self.start_span(
operation_name=operation_name,
child_of=child_of,
references=references,
tags=tags,
start_time=start_time,
ignore_active_span=ignore_active_span,
)
return self._scope_manager.activate(span, finish_on_close)

def start_span(
self,
operation_name=None,
child_of=None,
references=None,
tags=None,
start_time=None,
ignore_active_span=False,
):
# Use active span as parent when no explicit parent is specified.
if not ignore_active_span and not child_of:
child_of = self.active_span

# Use the specified parent or the active span if possible. Otherwise,
# use a `None` parent, which triggers the creation of a new trace.
parent = child_of.unwrap() if child_of else None
span = self._otel_tracer.create_span(operation_name, parent)

if references:
for ref in references:
span.add_link(ref.referenced_context.unwrap())

if tags:
for key, value in tags.items():
span.set_attribute(key, value)

# The OpenTracing API expects time values to be `float` values which
# represent the number of seconds since the epoch. OpenTelemetry
# represents time values as nanoseconds since the epoch.
start_time_ns = start_time
if start_time_ns is not None:
start_time_ns = util.time_seconds_to_ns(start_time)

span.start(start_time=start_time_ns)
context = SpanContextShim(span.get_context())
return SpanShim(self, context, span)

def inject(self, span_context, format, carrier):
# pylint: disable=redefined-builtin
logger.warning(
"Calling unimplemented method inject() on class %s",
self.__class__.__name__,
)
# TODO: Implement.

def extract(self, format, carrier):
# pylint: disable=redefined-builtin
logger.warning(
"Calling unimplemented method extract() on class %s",
self.__class__.__name__,
)
# TODO: Implement.
Loading

0 comments on commit e4d8949

Please sign in to comment.