Remove Opentracing Shim Instrumentation

This commit is contained in:
Nathaniel Ruiz Nowell 2020-11-04 09:14:37 -08:00
parent bcb19088ef
commit 39edb85a60
51 changed files with 0 additions and 3310 deletions

View File

@ -1,30 +0,0 @@
# Changelog
## Unreleased
## Version 0.13b0
Released 2020-09-17
- Drop support for Python 3.4
([#1099](https://github.com/open-telemetry/opentelemetry-python/pull/1099))
## Version 0.12b0
Released 2020-08-14
- Change reference names to opentelemetry-instrumentation-opentracing-shim
([#969](https://github.com/open-telemetry/opentelemetry-python/pull/969))
## 0.3a0
Released 2019-12-11
- Implement extract and inject support for HTTP_HEADERS and TEXT_MAP formats
([#256](https://github.com/open-telemetry/opentelemetry-python/pull/256))
## 0.2a0
Released 2019-10-29
- Initial release

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -1,9 +0,0 @@
graft src
graft tests
global-exclude *.pyc
global-exclude *.pyo
global-exclude __pycache__/*
include CHANGELOG.md
include MANIFEST.in
include README.rst
include LICENSE

View File

@ -1,20 +0,0 @@
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
----------
* `OpenTracing Shim for OpenTelemetry <https://opentelemetry-python.readthedocs.io/en/latest/instrumentation/opentracing_shim/opentracing_shim.html>`_
* `OpenTelemetry Project <https://opentelemetry.io/>`_

View File

@ -1,52 +0,0 @@
# 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.
#
[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/tree/master/instrumentation/opentelemetry-instrumentation-opentracing-shim
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 4 - Beta
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
[options]
python_requires = >=3.5
package_dir=
=src
packages=find_namespace:
install_requires =
Deprecated >= 1.2.6
opentracing ~= 2.0
opentelemetry-api == 0.15.dev0
[options.extras_require]
test =
opentelemetry-test == 0.15.dev0
opentracing ~= 2.2.0
[options.packages.find]
where = src

View File

@ -1,31 +0,0 @@
# 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 os
import setuptools
BASE_DIR = os.path.dirname(__file__)
VERSION_FILENAME = os.path.join(
BASE_DIR,
"src",
"opentelemetry",
"instrumentation",
"opentracing_shim",
"version.py",
)
PACKAGE_INFO = {}
with open(VERSION_FILENAME) as f:
exec(f.read(), PACKAGE_INFO)
setuptools.setup(version=PACKAGE_INFO["__version__"])

View File

@ -1,725 +0,0 @@
# 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.
"""
The OpenTelemetry OpenTracing shim is a library which allows an easy migration
from OpenTracing to OpenTelemetry.
The shim consists of a set of classes which implement the OpenTracing Python
API while using OpenTelemetry constructs behind the scenes. Its purpose is to
allow applications which are already instrumented using OpenTracing to start
using OpenTelemetry with a minimal effort, without having to rewrite large
portions of the codebase.
To use the shim, a :class:`TracerShim` instance is created and then used as if
it were an "ordinary" OpenTracing :class:`opentracing.Tracer`, as in the
following example::
import time
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.instrumentation.opentracing_shim import create_tracer
# Define which OpenTelemetry Tracer provider implementation to use.
trace.set_tracer_provider(TracerProvider())
# Create an OpenTelemetry Tracer.
otel_tracer = trace.get_tracer(__name__)
# Create an OpenTracing shim.
shim = create_tracer(otel_tracer)
with shim.start_active_span("ProcessHTTPRequest"):
print("Processing HTTP request")
# Sleeping to mock real work.
time.sleep(0.1)
with shim.start_active_span("GetDataFromDB"):
print("Getting data from DB")
# Sleeping to mock real work.
time.sleep(0.2)
Note:
While the OpenTracing Python API represents time values as the number of
**seconds** since the epoch expressed as :obj:`float` values, the
OpenTelemetry Python API represents time values as the number of
**nanoseconds** since the epoch expressed as :obj:`int` values. This fact
requires the OpenTracing shim to convert time values back and forth between
the two representations, which involves floating point arithmetic.
Due to the way computers represent floating point values in hardware,
representation of decimal floating point values in binary-based hardware is
imprecise by definition.
The above results in **slight imprecisions** in time values passed to the
shim via the OpenTracing API when comparing the value passed to the shim
and the value stored in the OpenTelemetry :class:`opentelemetry.trace.Span`
object behind the scenes. **This is not a bug in this library or in
Python**. Rather, this is a generic problem which stems from the fact that
not every decimal floating point number can be correctly represented in
binary, and therefore affects other libraries and programming languages as
well. More information about this problem can be found in the
`Floating Point Arithmetic\\: Issues and Limitations`_ section of the
Python documentation.
While testing this library, the aforementioned imprecisions were observed
to be of *less than a microsecond*.
API
---
.. _Floating Point Arithmetic\\: Issues and Limitations:
https://docs.python.org/3/tutorial/floatingpoint.html
"""
# TODO: make pylint use 3p opentracing module for type inference
# pylint:disable=no-member
import logging
from typing import Optional, TypeVar, Union
from deprecated import deprecated
from opentracing import (
Format,
Scope,
ScopeManager,
Span,
SpanContext,
Tracer,
UnsupportedFormatException,
)
from opentelemetry import propagators
from opentelemetry.baggage import get_baggage, set_baggage
from opentelemetry.context import Context, attach, detach, get_value, set_value
from opentelemetry.instrumentation.opentracing_shim import util
from opentelemetry.instrumentation.opentracing_shim.version import __version__
from opentelemetry.trace import INVALID_SPAN_CONTEXT, DefaultSpan, Link
from opentelemetry.trace import SpanContext as OtelSpanContext
from opentelemetry.trace import Tracer as OtelTracer
from opentelemetry.trace import (
TracerProvider,
get_current_span,
set_span_in_context,
)
from opentelemetry.util.types import Attributes
ValueT = TypeVar("ValueT", int, float, bool, str)
logger = logging.getLogger(__name__)
def create_tracer(otel_tracer_provider: TracerProvider) -> "TracerShim":
"""Creates a :class:`TracerShim` object from the provided OpenTelemetry
:class:`opentelemetry.trace.TracerProvider`.
The returned :class:`TracerShim` is an implementation of
:class:`opentracing.Tracer` using OpenTelemetry under the hood.
Args:
otel_tracer_provider: A tracer from this provider will be used to
perform the actual tracing when user code is instrumented using the
OpenTracing API.
Returns:
The created :class:`TracerShim`.
"""
return TracerShim(otel_tracer_provider.get_tracer(__name__, __version__))
class SpanContextShim(SpanContext):
"""Implements :class:`opentracing.SpanContext` by wrapping a
:class:`opentelemetry.trace.SpanContext` object.
Args:
otel_context: A :class:`opentelemetry.trace.SpanContext` to be used for
constructing the :class:`SpanContextShim`.
"""
def __init__(self, otel_context: OtelSpanContext):
self._otel_context = otel_context
# Context is being used here since it must be immutable.
self._baggage = Context()
def unwrap(self) -> OtelSpanContext:
"""Returns the wrapped :class:`opentelemetry.trace.SpanContext`
object.
Returns:
The :class:`opentelemetry.trace.SpanContext` object wrapped by this
:class:`SpanContextShim`.
"""
return self._otel_context
@property
def baggage(self) -> Context:
"""Returns the ``baggage`` associated with this object"""
return self._baggage
class SpanShim(Span):
"""Wraps a :class:`opentelemetry.trace.Span` object.
Args:
tracer: The :class:`opentracing.Tracer` that created this `SpanShim`.
context: A :class:`SpanContextShim` which contains the context for this
:class:`SpanShim`.
span: A :class:`opentelemetry.trace.Span` to wrap.
"""
def __init__(self, tracer, context: SpanContextShim, span):
super().__init__(tracer, context)
self._otel_span = span
def unwrap(self):
"""Returns the wrapped :class:`opentelemetry.trace.Span` object.
Returns:
The :class:`opentelemetry.trace.Span` object wrapped by this
:class:`SpanShim`.
"""
return self._otel_span
def set_operation_name(self, operation_name: str) -> "SpanShim":
"""Updates the name of the wrapped OpenTelemetry span.
Args:
operation_name: The new name to be used for the underlying
:class:`opentelemetry.trace.Span` object.
Returns:
Returns this :class:`SpanShim` instance to allow call chaining.
"""
self._otel_span.update_name(operation_name)
return self
def finish(self, finish_time: float = None):
"""Ends the OpenTelemetry span wrapped by this :class:`SpanShim`.
If *finish_time* is provided, the time value is converted to the
OpenTelemetry time format (number of nanoseconds since the epoch,
expressed as an integer) and passed on to the OpenTelemetry tracer when
ending the OpenTelemetry span. If *finish_time* isn't provided, it is
up to the OpenTelemetry tracer implementation to generate a timestamp
when ending the span.
Args:
finish_time: A value that represents the finish time expressed as
the number of seconds since the epoch as returned by
:func:`time.time()`.
"""
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: str, value: ValueT) -> "SpanShim":
"""Sets an OpenTelemetry attribute on the wrapped OpenTelemetry span.
Args:
key: A tag key.
value: A tag value.
Returns:
Returns this :class:`SpanShim` instance to allow call chaining.
"""
self._otel_span.set_attribute(key, value)
return self
def log_kv(
self, key_values: Attributes, timestamp: float = None
) -> "SpanShim":
"""Logs an event for the wrapped OpenTelemetry span.
Note:
The OpenTracing API defines the values of *key_values* to be of any
type. However, the OpenTelemetry API requires that the values be
any one of the types defined in
``opentelemetry.trace.util.Attributes`` therefore, only these types
are supported as values.
Args:
key_values: A dictionary as specified in
``opentelemetry.trace.util.Attributes``.
timestamp: Timestamp of the OpenTelemetry event, will be generated
automatically if omitted.
Returns:
Returns this :class:`SpanShim` instance to allow call chaining.
"""
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, key_values, event_timestamp)
return self
@deprecated(reason="This method is deprecated in favor of log_kv")
def log(self, **kwargs):
super().log(**kwargs)
@deprecated(reason="This method is deprecated in favor of log_kv")
def log_event(self, event, payload=None):
super().log_event(event, payload=payload)
def set_baggage_item(self, key: str, value: str):
"""Stores a Baggage item in the span as a key/value
pair.
Args:
key: A tag key.
value: A tag value.
"""
# pylint: disable=protected-access
self._context._baggage = set_baggage(
key, value, context=self._context._baggage
)
def get_baggage_item(self, key: str) -> Optional[object]:
"""Retrieves value of the baggage item with the given key.
Args:
key: A tag key.
Returns:
Returns this :class:`SpanShim` instance to allow call chaining.
"""
# pylint: disable=protected-access
return get_baggage(key, context=self._context._baggage)
class ScopeShim(Scope):
"""A `ScopeShim` wraps the OpenTelemetry functionality related to span
activation/deactivation while using OpenTracing :class:`opentracing.Scope`
objects for presentation.
Unlike other classes in this package, the `ScopeShim` class doesn't wrap an
OpenTelemetry class because OpenTelemetry doesn't have the notion of
"scope" (though it *does* have similar functionality).
There are two ways to construct a `ScopeShim` object: using the default
initializer and using the :meth:`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 an OpenTelemetry
`opentelemetry.trace.Span` context manager (as returned by
:meth:`opentelemetry.trace.Tracer.use_span`), in which case our only way of
retrieving a `opentelemetry.trace.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`. The former is used mainly
when the instrumentation code retrieves the currently-active span using
`ScopeManagerShim.active`. The latter is mainly used when the
instrumentation code activates a span using
:meth:`ScopeManagerShim.activate`.
Args:
manager: The :class:`ScopeManagerShim` that created this
:class:`ScopeShim`.
span: The :class:`SpanShim` this :class:`ScopeShim` controls.
span_cm: A Python context manager which yields an OpenTelemetry
`opentelemetry.trace.Span` from its ``__enter__()`` method. Used
by :meth:`from_context_manager` to store the context manager as
an attribute so that it can later be closed by calling its
``__exit__()`` method. Defaults to `None`.
"""
def __init__(
self, manager: "ScopeManagerShim", span: SpanShim, span_cm=None
):
super().__init__(manager, span)
self._span_cm = span_cm
self._token = attach(set_value("scope_shim", self))
# 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: "ScopeManagerShim", span_cm):
"""Constructs a :class:`ScopeShim` from an OpenTelemetry
`opentelemetry.trace.Span` context
manager.
The method extracts a `opentelemetry.trace.Span` object from the
context manager by calling the context manager's ``__enter__()``
method. This causes the span to start in the OpenTelemetry tracer.
Example usage::
span = otel_tracer.start_span("TestSpan")
span_cm = otel_tracer.use_span(span)
scope_shim = ScopeShim.from_context_manager(
scope_manager_shim,
span_cm=span_cm,
)
Args:
manager: The :class:`ScopeManagerShim` that created this
:class:`ScopeShim`.
span_cm: A context manager as returned by
:meth:`opentelemetry.trace.Tracer.use_span`.
"""
otel_span = span_cm.__enter__()
span_context = SpanContextShim(otel_span.get_span_context())
span = SpanShim(manager.tracer, span_context, otel_span)
return cls(manager, span, span_cm)
def close(self):
"""Closes the `ScopeShim`. If the `ScopeShim` was created from a
context manager, calling this method sets the active span in the
OpenTelemetry tracer back to the span which was active before this
`ScopeShim` was created. In addition, if the span represented by this
`ScopeShim` was activated with the *finish_on_close* argument set to
`True`, calling this method will end the span.
Warning:
In the current state of the implementation it is possible to create
a `ScopeShim` directly from a `SpanShim`, that is - without using
:meth:`from_context_manager()`. For that reason we need to be able
to end the span represented by the `ScopeShim` in this case, too.
Please note that closing a `ScopeShim` created this way (for
example as returned by :meth:`ScopeManagerShim.active`) **always
ends the associated span**, regardless of the value passed in
*finish_on_close* when activating the span.
"""
detach(self._token)
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(ScopeManager):
"""Implements :class:`opentracing.ScopeManager` by setting and getting the
active `opentelemetry.trace.Span` in the OpenTelemetry tracer.
This class keeps a reference to a :class:`TracerShim` as an attribute. This
reference is used to communicate with the OpenTelemetry tracer. It is
necessary to have a reference to the :class:`TracerShim` rather than the
:class:`opentelemetry.trace.Tracer` wrapped by it because when constructing
a :class:`SpanShim` we need to pass a reference to a
:class:`opentracing.Tracer`.
Args:
tracer: A :class:`TracerShim` to use for setting and getting active
span state.
"""
def __init__(self, tracer: "TracerShim"):
# 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: SpanShim, finish_on_close: bool) -> "ScopeShim":
"""Activates a :class:`SpanShim` and returns a :class:`ScopeShim` which
represents the active span.
Args:
span: A :class:`SpanShim` to be activated.
finish_on_close(:obj:`bool`): Determines whether the OpenTelemetry
span should be ended when the returned :class:`ScopeShim` is
closed.
Returns:
A :class:`ScopeShim` representing the activated span.
"""
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) -> "ScopeShim":
"""Returns a :class:`ScopeShim` object representing the
currently-active span in the OpenTelemetry tracer.
Returns:
A :class:`ScopeShim` representing the active span in the
OpenTelemetry tracer, or `None` if no span is currently active.
Warning:
Calling :meth:`ScopeShim.close` on the :class:`ScopeShim` returned
by this property **always ends the corresponding span**, regardless
of the *finish_on_close* value used when activating the span. This
is a limitation of the current implementation of the OpenTracing
shim and is likely to be handled in future versions.
"""
span = get_current_span()
if span.get_span_context() == INVALID_SPAN_CONTEXT:
return None
try:
return get_value("scope_shim")
except KeyError:
span_context = SpanContextShim(span.get_span_context())
wrapped_span = SpanShim(self._tracer, span_context, span)
return ScopeShim(self, span=wrapped_span)
@property
def tracer(self) -> "TracerShim":
"""Returns the :class:`TracerShim` reference used by this
:class:`ScopeManagerShim` for setting and getting the active span from
the OpenTelemetry tracer.
Returns:
The :class:`TracerShim` used for setting and getting the active
span.
Warning:
This property is *not* a part of the OpenTracing API. It used
internally by the current implementation of the OpenTracing shim
and will likely be removed in future versions.
"""
return self._tracer
class TracerShim(Tracer):
"""Wraps a :class:`opentelemetry.trace.Tracer` object.
This wrapper class allows using an OpenTelemetry tracer as if it were an
OpenTracing tracer. It exposes the same methods as an "ordinary"
OpenTracing tracer, and uses OpenTelemetry transparently for performing the
actual tracing.
This class depends on the *OpenTelemetry API*. Therefore, any
implementation of a :class:`opentelemetry.trace.Tracer` should work with
this class.
Args:
tracer: A :class:`opentelemetry.trace.Tracer` to use for tracing. This
tracer will be invoked by the shim to create actual spans.
"""
def __init__(self, tracer: OtelTracer):
super().__init__(scope_manager=ScopeManagerShim(self))
self._otel_tracer = tracer
self._supported_formats = (
Format.TEXT_MAP,
Format.HTTP_HEADERS,
)
def unwrap(self):
"""Returns the :class:`opentelemetry.trace.Tracer` object that is
wrapped by this :class:`TracerShim` and used for actual tracing.
Returns:
The :class:`opentelemetry.trace.Tracer` used for actual tracing.
"""
return self._otel_tracer
def start_active_span(
self,
operation_name: str,
child_of: Union[SpanShim, SpanContextShim] = None,
references: list = None,
tags: Attributes = None,
start_time: float = None,
ignore_active_span: bool = False,
finish_on_close: bool = True,
) -> "ScopeShim":
"""Starts and activates a span. In terms of functionality, this method
behaves exactly like the same method on a "regular" OpenTracing tracer.
See :meth:`opentracing.Tracer.start_active_span` for more details.
Args:
operation_name: Name of the operation represented by
the new span from the perspective of the current service.
child_of: A :class:`SpanShim` or :class:`SpanContextShim`
representing the parent in a "child of" reference. If
specified, the *references* parameter must be omitted.
references: A list of :class:`opentracing.Reference` objects that
identify one or more parents of type :class:`SpanContextShim`.
tags: A dictionary of tags.
start_time: An explicit start time expressed as the number of
seconds since the epoch as returned by :func:`time.time()`.
ignore_active_span: Ignore the currently-active span in the
OpenTelemetry tracer and make the created span the root span of
a new trace.
finish_on_close: Determines whether the created span should end
automatically when closing the returned :class:`ScopeShim`.
Returns:
A :class:`ScopeShim` that is already activated by the
:class:`ScopeManagerShim`.
"""
current_span = get_current_span()
if child_of is None and current_span is not INVALID_SPAN_CONTEXT:
child_of = SpanShim(None, None, current_span)
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: str = None,
child_of: Union[SpanShim, SpanContextShim] = None,
references: list = None,
tags: Attributes = None,
start_time: float = None,
ignore_active_span: bool = False,
) -> SpanShim:
"""Implements the ``start_span()`` method from the base class.
Starts a span. In terms of functionality, this method behaves exactly
like the same method on a "regular" OpenTracing tracer. See
:meth:`opentracing.Tracer.start_span` for more details.
Args:
operation_name: Name of the operation represented by the new span
from the perspective of the current service.
child_of: A :class:`SpanShim` or :class:`SpanContextShim`
representing the parent in a "child of" reference. If
specified, the *references* parameter must be omitted.
references: A list of :class:`opentracing.Reference` objects that
identify one or more parents of type :class:`SpanContextShim`.
tags: A dictionary of tags.
start_time: An explicit start time expressed as the number of
seconds since the epoch as returned by :func:`time.time()`.
ignore_active_span: Ignore the currently-active span in the
OpenTelemetry tracer and make the created span the root span of
a new trace.
Returns:
An already-started :class:`SpanShim` instance.
"""
# 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
if isinstance(parent, OtelSpanContext):
parent = DefaultSpan(parent)
parent_span_context = set_span_in_context(parent)
links = []
if references:
for ref in references:
links.append(Link(ref.referenced_context.unwrap()))
# 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 = self._otel_tracer.start_span(
operation_name,
context=parent_span_context,
links=links,
attributes=tags,
start_time=start_time_ns,
)
context = SpanContextShim(span.get_span_context())
return SpanShim(self, context, span)
def inject(self, span_context, format: object, carrier: object):
"""Injects ``span_context`` into ``carrier``.
See base class for more details.
Args:
span_context: The ``opentracing.SpanContext`` to inject.
format: a Python object instance that represents a given
carrier format. `format` may be of any type, and `format`
equality is defined by Python ``==`` operator.
carrier: the format-specific carrier object to inject into
"""
# pylint: disable=redefined-builtin
# This implementation does not perform the injecting by itself but
# uses the configured propagators in opentelemetry.propagators.
# TODO: Support Format.BINARY once it is supported in
# opentelemetry-python.
if format not in self._supported_formats:
raise UnsupportedFormatException
propagator = propagators.get_global_textmap()
ctx = set_span_in_context(DefaultSpan(span_context.unwrap()))
propagator.inject(type(carrier).__setitem__, carrier, context=ctx)
def extract(self, format: object, carrier: object):
"""Returns an ``opentracing.SpanContext`` instance extracted from a
``carrier``.
See base class for more details.
Args:
format: a Python object instance that represents a given
carrier format. ``format`` may be of any type, and ``format``
equality is defined by python ``==`` operator.
carrier: the format-specific carrier object to extract from
Returns:
An ``opentracing.SpanContext`` extracted from ``carrier`` or
``None`` if no such ``SpanContext`` could be found.
"""
# pylint: disable=redefined-builtin
# This implementation does not perform the extracing by itself but
# uses the configured propagators in opentelemetry.propagators.
# TODO: Support Format.BINARY once it is supported in
# opentelemetry-python.
if format not in self._supported_formats:
raise UnsupportedFormatException
def get_as_list(dict_object, key):
value = dict_object.get(key)
return [value] if value is not None else []
propagator = propagators.get_global_textmap()
ctx = propagator.extract(get_as_list, carrier)
span = get_current_span(ctx)
if span is not None:
otel_context = span.get_span_context()
else:
otel_context = INVALID_SPAN_CONTEXT
return SpanContextShim(otel_context)

View File

@ -1,54 +0,0 @@
# 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.
# A default event name to be used for logging events when a better event name
# can't be derived from the event's key-value pairs.
DEFAULT_EVENT_NAME = "log"
def time_seconds_to_ns(time_seconds):
"""Converts a time value in seconds to a time value in nanoseconds.
`time_seconds` is a `float` as returned by `time.time()` which represents
the number of seconds since the epoch.
The returned value is an `int` representing the number of nanoseconds since
the epoch.
"""
return int(time_seconds * 1e9)
def time_seconds_from_ns(time_nanoseconds):
"""Converts a time value in nanoseconds to a time value in seconds.
`time_nanoseconds` is an `int` representing the number of nanoseconds since
the epoch.
The returned value is a `float` representing the number of seconds since
the epoch.
"""
return time_nanoseconds / 1e9
def event_name_from_kv(key_values):
"""A helper function which returns an event name from the given dict, or a
default event name.
"""
if key_values is None or "event" not in key_values:
return DEFAULT_EVENT_NAME
return key_values["event"]

View File

@ -1,15 +0,0 @@
# 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.
__version__ = "0.15.dev0"

View File

@ -1,636 +0,0 @@
# 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.
# TODO: make pylint use 3p opentracing module for type inference
# pylint:disable=no-member
import time
from unittest import TestCase
from unittest.mock import Mock
import opentracing
from opentelemetry import propagators, trace
from opentelemetry.instrumentation.opentracing_shim import (
SpanContextShim,
SpanShim,
create_tracer,
util,
)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.test.mock_textmap import (
MockTextMapPropagator,
NOOPTextMapPropagator,
)
class TestShim(TestCase):
# pylint: disable=too-many-public-methods
def setUp(self):
"""Create an OpenTelemetry tracer and a shim before every test case."""
trace.set_tracer_provider(TracerProvider())
self.shim = create_tracer(trace.get_tracer_provider())
@classmethod
def setUpClass(cls):
# Save current propagator to be restored on teardown.
cls._previous_propagator = propagators.get_global_textmap()
# Set mock propagator for testing.
propagators.set_global_textmap(MockTextMapPropagator())
@classmethod
def tearDownClass(cls):
# Restore previous propagator.
propagators.set_global_textmap(cls._previous_propagator)
def test_shim_type(self):
# Verify shim is an OpenTracing tracer.
self.assertIsInstance(self.shim, opentracing.Tracer)
def test_start_active_span(self):
"""Test span creation and activation using `start_active_span()`."""
with self.shim.start_active_span("TestSpan0") as scope:
# Verify correct type of Scope and Span objects.
self.assertIsInstance(scope, opentracing.Scope)
self.assertIsInstance(scope.span, opentracing.Span)
# Verify span is started.
self.assertIsNotNone(scope.span.unwrap().start_time)
# Verify span is active.
self.assertEqual(
self.shim.active_span.context.unwrap(),
scope.span.context.unwrap(),
)
# TODO: We can't check for equality of self.shim.active_span and
# scope.span because the same OpenTelemetry span is returned inside
# different SpanShim objects. A possible solution is described
# here:
# https://github.com/open-telemetry/opentelemetry-python/issues/161#issuecomment-534136274
# Verify span has ended.
self.assertIsNotNone(scope.span.unwrap().end_time)
# Verify no span is active.
self.assertIsNone(self.shim.active_span)
def test_start_span(self):
"""Test span creation using `start_span()`."""
with self.shim.start_span("TestSpan1") as span:
# Verify correct type of Span object.
self.assertIsInstance(span, opentracing.Span)
# Verify span is started.
self.assertIsNotNone(span.unwrap().start_time)
# Verify `start_span()` does NOT make the span active.
self.assertIsNone(self.shim.active_span)
# Verify span has ended.
self.assertIsNotNone(span.unwrap().end_time)
def test_start_span_no_contextmanager(self):
"""Test `start_span()` without a `with` statement."""
span = self.shim.start_span("TestSpan2")
# Verify span is started.
self.assertIsNotNone(span.unwrap().start_time)
# Verify `start_span()` does NOT make the span active.
self.assertIsNone(self.shim.active_span)
span.finish()
def test_explicit_span_finish(self):
"""Test `finish()` method on `Span` objects."""
span = self.shim.start_span("TestSpan3")
# Verify span hasn't ended.
self.assertIsNone(span.unwrap().end_time)
span.finish()
# Verify span has ended.
self.assertIsNotNone(span.unwrap().end_time)
def test_explicit_start_time(self):
"""Test `start_time` argument."""
now = time.time()
with self.shim.start_active_span("TestSpan4", start_time=now) as scope:
result = util.time_seconds_from_ns(scope.span.unwrap().start_time)
# Tolerate inaccuracies of less than a microsecond. See Note:
# https://open-telemetry.github.io/opentelemetry-python/opentelemetry.instrumentation.opentracing_shim.html
# TODO: This seems to work consistently, but we should find out the
# biggest possible loss of precision.
self.assertAlmostEqual(result, now, places=6)
def test_explicit_end_time(self):
"""Test `end_time` argument of `finish()` method."""
span = self.shim.start_span("TestSpan5")
now = time.time()
span.finish(now)
end_time = util.time_seconds_from_ns(span.unwrap().end_time)
# Tolerate inaccuracies of less than a microsecond. See Note:
# https://open-telemetry.github.io/opentelemetry-python/opentelemetry.instrumentation.opentracing_shim.html
# TODO: This seems to work consistently, but we should find out the
# biggest possible loss of precision.
self.assertAlmostEqual(end_time, now, places=6)
def test_explicit_span_activation(self):
"""Test manual activation and deactivation of a span."""
span = self.shim.start_span("TestSpan6")
# Verify no span is currently active.
self.assertIsNone(self.shim.active_span)
with self.shim.scope_manager.activate(
span, finish_on_close=True
) as scope:
# Verify span is active.
self.assertEqual(
self.shim.active_span.context.unwrap(),
scope.span.context.unwrap(),
)
# Verify no span is active.
self.assertIsNone(self.shim.active_span)
def test_start_active_span_finish_on_close(self):
"""Test `finish_on_close` argument of `start_active_span()`."""
with self.shim.start_active_span(
"TestSpan7", finish_on_close=True
) as scope:
# Verify span hasn't ended.
self.assertIsNone(scope.span.unwrap().end_time)
# Verify span has ended.
self.assertIsNotNone(scope.span.unwrap().end_time)
with self.shim.start_active_span(
"TestSpan8", finish_on_close=False
) as scope:
# Verify span hasn't ended.
self.assertIsNone(scope.span.unwrap().end_time)
# Verify span hasn't ended after scope had been closed.
self.assertIsNone(scope.span.unwrap().end_time)
scope.span.finish()
def test_activate_finish_on_close(self):
"""Test `finish_on_close` argument of `activate()`."""
span = self.shim.start_span("TestSpan9")
with self.shim.scope_manager.activate(
span, finish_on_close=True
) as scope:
# Verify span is active.
self.assertEqual(
self.shim.active_span.context.unwrap(),
scope.span.context.unwrap(),
)
# Verify span has ended.
self.assertIsNotNone(span.unwrap().end_time)
span = self.shim.start_span("TestSpan10")
with self.shim.scope_manager.activate(
span, finish_on_close=False
) as scope:
# Verify span is active.
self.assertEqual(
self.shim.active_span.context.unwrap(),
scope.span.context.unwrap(),
)
# Verify span hasn't ended.
self.assertIsNone(span.unwrap().end_time)
span.finish()
def test_explicit_scope_close(self):
"""Test `close()` method on `ScopeShim`."""
with self.shim.start_active_span("ParentSpan") as parent:
# Verify parent span is active.
self.assertEqual(
self.shim.active_span.context.unwrap(),
parent.span.context.unwrap(),
)
child = self.shim.start_active_span("ChildSpan")
# Verify child span is active.
self.assertEqual(
self.shim.active_span.context.unwrap(),
child.span.context.unwrap(),
)
# Verify child span hasn't ended.
self.assertIsNone(child.span.unwrap().end_time)
child.close()
# Verify child span has ended.
self.assertIsNotNone(child.span.unwrap().end_time)
# Verify parent span becomes active again.
self.assertEqual(
self.shim.active_span.context.unwrap(),
parent.span.context.unwrap(),
)
def test_parent_child_implicit(self):
"""Test parent-child relationship and activation/deactivation of spans
without specifying the parent span upon creation.
"""
with self.shim.start_active_span("ParentSpan") as parent:
# Verify parent span is the active span.
self.assertEqual(
self.shim.active_span.context.unwrap(),
parent.span.context.unwrap(),
)
with self.shim.start_active_span("ChildSpan") as child:
# Verify child span is the active span.
self.assertEqual(
self.shim.active_span.context.unwrap(),
child.span.context.unwrap(),
)
# Verify parent-child relationship.
parent_trace_id = (
parent.span.unwrap().get_span_context().trace_id
)
child_trace_id = (
child.span.unwrap().get_span_context().trace_id
)
self.assertEqual(parent_trace_id, child_trace_id)
self.assertEqual(
child.span.unwrap().parent,
parent.span.unwrap().get_span_context(),
)
# Verify parent span becomes the active span again.
self.assertEqual(
self.shim.active_span.context.unwrap(),
parent.span.context.unwrap()
# TODO: Check equality of the spans themselves rather than
# their context once the SpanShim reconstruction problem has
# been addressed (see previous TODO).
)
# Verify there is no active span.
self.assertIsNone(self.shim.active_span)
def test_parent_child_explicit_span(self):
"""Test parent-child relationship of spans when specifying a `Span`
object as a parent upon creation.
"""
with self.shim.start_span("ParentSpan") as parent:
with self.shim.start_active_span(
"ChildSpan", child_of=parent
) as child:
parent_trace_id = parent.unwrap().get_span_context().trace_id
child_trace_id = (
child.span.unwrap().get_span_context().trace_id
)
self.assertEqual(child_trace_id, parent_trace_id)
self.assertEqual(
child.span.unwrap().parent,
parent.unwrap().get_span_context(),
)
with self.shim.start_span("ParentSpan") as parent:
child = self.shim.start_span("ChildSpan", child_of=parent)
parent_trace_id = parent.unwrap().get_span_context().trace_id
child_trace_id = child.unwrap().get_span_context().trace_id
self.assertEqual(child_trace_id, parent_trace_id)
self.assertEqual(
child.unwrap().parent, parent.unwrap().get_span_context()
)
child.finish()
def test_parent_child_explicit_span_context(self):
"""Test parent-child relationship of spans when specifying a
`SpanContext` object as a parent upon creation.
"""
with self.shim.start_span("ParentSpan") as parent:
with self.shim.start_active_span(
"ChildSpan", child_of=parent.context
) as child:
parent_trace_id = parent.unwrap().get_span_context().trace_id
child_trace_id = (
child.span.unwrap().get_span_context().trace_id
)
self.assertEqual(child_trace_id, parent_trace_id)
self.assertEqual(
child.span.unwrap().parent, parent.context.unwrap()
)
with self.shim.start_span("ParentSpan") as parent:
with self.shim.start_span(
"SpanWithContextParent", child_of=parent.context
) as child:
parent_trace_id = parent.unwrap().get_span_context().trace_id
child_trace_id = child.unwrap().get_span_context().trace_id
self.assertEqual(child_trace_id, parent_trace_id)
self.assertEqual(
child.unwrap().parent, parent.context.unwrap()
)
def test_references(self):
"""Test span creation using the `references` argument."""
with self.shim.start_span("ParentSpan") as parent:
ref = opentracing.child_of(parent.context)
with self.shim.start_active_span(
"ChildSpan", references=[ref]
) as child:
self.assertEqual(
child.span.unwrap().links[0].context,
parent.context.unwrap(),
)
def test_set_operation_name(self):
"""Test `set_operation_name()` method."""
with self.shim.start_active_span("TestName") as scope:
self.assertEqual(scope.span.unwrap().name, "TestName")
scope.span.set_operation_name("NewName")
self.assertEqual(scope.span.unwrap().name, "NewName")
def test_tags(self):
"""Test tags behavior using the `tags` argument and the `set_tags()`
method.
"""
tags = {"foo": "bar"}
with self.shim.start_active_span("TestSetTag", tags=tags) as scope:
scope.span.set_tag("baz", "qux")
self.assertEqual(scope.span.unwrap().attributes["foo"], "bar")
self.assertEqual(scope.span.unwrap().attributes["baz"], "qux")
def test_span_tracer(self):
"""Test the `tracer` property on `Span` objects."""
with self.shim.start_active_span("TestSpan11") as scope:
self.assertEqual(scope.span.tracer, self.shim)
def test_log_kv(self):
"""Test the `log_kv()` method on `Span` objects."""
with self.shim.start_span("TestSpan12") as span:
span.log_kv({"foo": "bar"})
self.assertEqual(span.unwrap().events[0].attributes["foo"], "bar")
# Verify timestamp was generated automatically.
self.assertIsNotNone(span.unwrap().events[0].timestamp)
# Test explicit timestamp.
now = time.time()
span.log_kv({"foo": "bar"}, now)
result = util.time_seconds_from_ns(
span.unwrap().events[1].timestamp
)
self.assertEqual(span.unwrap().events[1].attributes["foo"], "bar")
# Tolerate inaccuracies of less than a microsecond. See Note:
# https://open-telemetry.github.io/opentelemetry-python/instrumentation/opentracing_shim/opentracing_shim.html
# TODO: This seems to work consistently, but we should find out the
# biggest possible loss of precision.
self.assertAlmostEqual(result, now, places=6)
def test_log(self):
"""Test the deprecated `log` method on `Span` objects."""
with self.shim.start_span("TestSpan13") as span:
with self.assertWarns(DeprecationWarning):
span.log(event="foo", payload="bar")
self.assertEqual(span.unwrap().events[0].attributes["event"], "foo")
self.assertEqual(span.unwrap().events[0].attributes["payload"], "bar")
self.assertIsNotNone(span.unwrap().events[0].timestamp)
def test_log_event(self):
"""Test the deprecated `log_event` method on `Span` objects."""
with self.shim.start_span("TestSpan14") as span:
with self.assertWarns(DeprecationWarning):
span.log_event("foo", "bar")
self.assertEqual(span.unwrap().events[0].attributes["event"], "foo")
self.assertEqual(span.unwrap().events[0].attributes["payload"], "bar")
self.assertIsNotNone(span.unwrap().events[0].timestamp)
def test_span_context(self):
"""Test construction of `SpanContextShim` objects."""
otel_context = trace.SpanContext(1234, 5678, is_remote=False)
context = SpanContextShim(otel_context)
self.assertIsInstance(context, opentracing.SpanContext)
self.assertEqual(context.unwrap().trace_id, 1234)
self.assertEqual(context.unwrap().span_id, 5678)
def test_span_on_error(self):
"""Verify error tag and logs are created on span when an exception is
raised.
"""
# Raise an exception while a span is active.
with self.assertRaises(Exception):
with self.shim.start_active_span("TestName") as scope:
raise Exception
# Verify exception details have been added to span.
self.assertEqual(scope.span.unwrap().attributes["error"], True)
def test_inject_http_headers(self):
"""Test `inject()` method for Format.HTTP_HEADERS."""
otel_context = trace.SpanContext(
trace_id=1220, span_id=7478, is_remote=False
)
context = SpanContextShim(otel_context)
headers = {}
self.shim.inject(context, opentracing.Format.HTTP_HEADERS, headers)
self.assertEqual(
headers[MockTextMapPropagator.TRACE_ID_KEY], str(1220)
)
self.assertEqual(headers[MockTextMapPropagator.SPAN_ID_KEY], str(7478))
def test_inject_text_map(self):
"""Test `inject()` method for Format.TEXT_MAP."""
otel_context = trace.SpanContext(
trace_id=1220, span_id=7478, is_remote=False
)
context = SpanContextShim(otel_context)
# Verify Format.TEXT_MAP
text_map = {}
self.shim.inject(context, opentracing.Format.TEXT_MAP, text_map)
self.assertEqual(
text_map[MockTextMapPropagator.TRACE_ID_KEY], str(1220)
)
self.assertEqual(
text_map[MockTextMapPropagator.SPAN_ID_KEY], str(7478)
)
def test_inject_binary(self):
"""Test `inject()` method for Format.BINARY."""
otel_context = trace.SpanContext(
trace_id=1220, span_id=7478, is_remote=False
)
context = SpanContextShim(otel_context)
# Verify exception for non supported binary format.
with self.assertRaises(opentracing.UnsupportedFormatException):
self.shim.inject(context, opentracing.Format.BINARY, bytearray())
def test_extract_http_headers(self):
"""Test `extract()` method for Format.HTTP_HEADERS."""
carrier = {
MockTextMapPropagator.TRACE_ID_KEY: 1220,
MockTextMapPropagator.SPAN_ID_KEY: 7478,
}
ctx = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier)
self.assertEqual(ctx.unwrap().trace_id, 1220)
self.assertEqual(ctx.unwrap().span_id, 7478)
def test_extract_empty_context_returns_invalid_context(self):
"""In the case where the propagator cannot extract a
SpanContext, extract should return and invalid span context.
"""
_old_propagator = propagators.get_global_textmap()
propagators.set_global_textmap(NOOPTextMapPropagator())
try:
carrier = {}
ctx = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier)
self.assertEqual(ctx.unwrap(), trace.INVALID_SPAN_CONTEXT)
finally:
propagators.set_global_textmap(_old_propagator)
def test_extract_text_map(self):
"""Test `extract()` method for Format.TEXT_MAP."""
carrier = {
MockTextMapPropagator.TRACE_ID_KEY: 1220,
MockTextMapPropagator.SPAN_ID_KEY: 7478,
}
ctx = self.shim.extract(opentracing.Format.TEXT_MAP, carrier)
self.assertEqual(ctx.unwrap().trace_id, 1220)
self.assertEqual(ctx.unwrap().span_id, 7478)
def test_extract_binary(self):
"""Test `extract()` method for Format.BINARY."""
# Verify exception for non supported binary format.
with self.assertRaises(opentracing.UnsupportedFormatException):
self.shim.extract(opentracing.Format.BINARY, bytearray())
def test_baggage(self):
span_context_shim = SpanContextShim(
trace.SpanContext(1234, 5678, is_remote=False)
)
baggage = span_context_shim.baggage
with self.assertRaises(ValueError):
baggage[1] = 3
span_shim = SpanShim(Mock(), span_context_shim, Mock())
span_shim.set_baggage_item(1, 2)
self.assertTrue(span_shim.get_baggage_item(1), 2)
def test_active(self):
"""Test that the active property and start_active_span return the same
object"""
# Verify no span is currently active.
self.assertIsNone(self.shim.active_span)
with self.shim.start_active_span("TestSpan15") as scope:
# Verify span is active.
self.assertEqual(
self.shim.active_span.context.unwrap(),
scope.span.context.unwrap(),
)
self.assertIs(self.shim.scope_manager.active, scope)
# Verify no span is active.
self.assertIsNone(self.shim.active_span)
def test_mixed_mode(self):
"""Test that span parent-child relationship is kept between
OpenTelemetry and the OpenTracing shim"""
span_shim = self.shim.start_span("TestSpan16")
with self.shim.scope_manager.activate(span_shim, finish_on_close=True):
with (
TracerProvider()
.get_tracer(__name__)
.start_as_current_span("abc")
) as opentelemetry_span:
self.assertIs(
span_shim.unwrap().context, opentelemetry_span.parent,
)
with (
TracerProvider().get_tracer(__name__).start_as_current_span("abc")
) as opentelemetry_span:
with self.shim.start_active_span("TestSpan17") as scope:
self.assertIs(
scope.span.unwrap().parent, opentelemetry_span.context,
)

View File

@ -1,66 +0,0 @@
# 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 time
import unittest
from opentelemetry.instrumentation.opentracing_shim import util
from opentelemetry.util import time_ns
class TestUtil(unittest.TestCase):
def test_event_name_from_kv(self):
# Test basic behavior.
event_name = "send HTTP request"
res = util.event_name_from_kv({"event": event_name, "foo": "bar"})
self.assertEqual(res, event_name)
# Test None.
res = util.event_name_from_kv(None)
self.assertEqual(res, util.DEFAULT_EVENT_NAME)
# Test empty dict.
res = util.event_name_from_kv({})
self.assertEqual(res, util.DEFAULT_EVENT_NAME)
# Test missing `event` field.
res = util.event_name_from_kv({"foo": "bar"})
self.assertEqual(res, util.DEFAULT_EVENT_NAME)
def test_time_seconds_to_ns(self):
time_seconds = time.time()
result = util.time_seconds_to_ns(time_seconds)
self.assertEqual(result, int(time_seconds * 1e9))
def test_time_seconds_from_ns(self):
time_nanoseconds = time_ns()
result = util.time_seconds_from_ns(time_nanoseconds)
self.assertEqual(result, time_nanoseconds / 1e9)
def test_time_conversion_precision(self):
"""Verify time conversion from seconds to nanoseconds and vice versa is
accurate enough.
"""
time_seconds = 1570484241.9501917
time_nanoseconds = util.time_seconds_to_ns(time_seconds)
result = util.time_seconds_from_ns(time_nanoseconds)
# Tolerate inaccuracies of less than a microsecond.
# TODO: Put a link to an explanation in the docs.
# TODO: This seems to work consistently, but we should find out the
# biggest possible loss of precision.
self.assertAlmostEqual(result, time_seconds, places=6)

View File

@ -1,47 +0,0 @@
Testbed suite for the OpenTelemetry-OpenTracing Bridge
======================================================
Testbed suite designed to test the API changes.
Build and test.
---------------
.. code-block:: sh
tox -e py37-test-opentracing-shim
Alternatively, due to the organization of the suite, it's possible to run directly the tests using ``py.test``\ :
.. code-block:: sh
py.test -s testbed/test_multiple_callbacks/test_threads.py
Tested frameworks
-----------------
Currently the examples cover ``threading`` and ``asyncio``.
List of patterns
----------------
* `Active Span replacement <test_active_span_replacement>`_ - Start an isolated task and query for its results in another task/thread.
* `Client-Server <test_client_server>`_ - Typical client-server example.
* `Common Request Handler <test_common_request_handler>`_ - One request handler for all requests.
* `Late Span finish <test_late_span_finish>`_ - Late parent ``Span`` finish.
* `Multiple callbacks <test_multiple_callbacks>`_ - Multiple callbacks spawned at the same time.
* `Nested callbacks <test_nested_callbacks>`_ - One callback at a time, defined in a pipeline fashion.
* `Subtask Span propagation <test_subtask_span_propagation>`_ - ``Span`` propagation for subtasks/coroutines.
Adding new patterns
-------------------
A new pattern is composed of a directory under *testbed* with the *test_* prefix, and containing the files for each platform, also with the *test_* prefix:
.. code-block::
testbed/
test_new_pattern/
test_threads.py
test_asyncio.py

View File

@ -1,26 +0,0 @@
import opentelemetry.instrumentation.opentracing_shim as opentracingshim
from opentelemetry.sdk import trace
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
class MockTracer(opentracingshim.TracerShim):
"""Wrapper of `opentracingshim.TracerShim`.
MockTracer extends `opentracingshim.TracerShim` by adding a in memory
span exporter that can be used to get the list of finished spans."""
def __init__(self):
tracer_provider = trace.TracerProvider()
oteltracer = tracer_provider.get_tracer(__name__)
super(MockTracer, self).__init__(oteltracer)
exporter = InMemorySpanExporter()
span_processor = SimpleExportSpanProcessor(exporter)
tracer_provider.add_span_processor(span_processor)
self.exporter = exporter
def finished_spans(self):
return self.exporter.get_finished_spans()

View File

@ -1,20 +0,0 @@
Active Span replacement example.
================================
This example shows a ``Span`` being created and then passed to an asynchronous task, which will temporary activate it to finish its processing, and further restore the previously active ``Span``.
``threading`` implementation:
.. code-block:: python
# Create a new Span for this task
with self.tracer.start_active_span("task"):
with self.tracer.scope_manager.activate(span, True):
# Simulate work strictly related to the initial Span
pass
# Use the task span as parent of a new subtask
with self.tracer.start_active_span("subtask"):
pass

View File

@ -1,54 +0,0 @@
from __future__ import print_function
import asyncio
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import stop_loop_when
class TestAsyncio(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.loop = asyncio.get_event_loop()
def test_main(self):
# Start an isolated task and query for its result -and finish it-
# in another task/thread
span = self.tracer.start_span("initial")
self.submit_another_task(span)
stop_loop_when(
self.loop,
lambda: len(self.tracer.finished_spans()) >= 3,
timeout=5.0,
)
self.loop.run_forever()
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 3)
self.assertNamesEqual(spans, ["initial", "subtask", "task"])
# task/subtask are part of the same trace,
# and subtask is a child of task
self.assertSameTrace(spans[1], spans[2])
self.assertIsChildOf(spans[1], spans[2])
# initial task is not related in any way to those two tasks
self.assertNotSameTrace(spans[0], spans[1])
self.assertEqual(spans[0].parent, None)
async def task(self, span):
# Create a new Span for this task
with self.tracer.start_active_span("task"):
with self.tracer.scope_manager.activate(span, True):
# Simulate work strictly related to the initial Span
pass
# Use the task span as parent of a new subtask
with self.tracer.start_active_span("subtask"):
pass
def submit_another_task(self, span):
self.loop.create_task(self.task(span))

View File

@ -1,50 +0,0 @@
from __future__ import print_function
from concurrent.futures import ThreadPoolExecutor
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
class TestThreads(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
# use max_workers=3 as a general example even if only one would suffice
self.executor = ThreadPoolExecutor(max_workers=3)
def test_main(self):
# Start an isolated task and query for its result -and finish it-
# in another task/thread
span = self.tracer.start_span("initial")
self.submit_another_task(span)
self.executor.shutdown(True)
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 3)
self.assertNamesEqual(spans, ["initial", "subtask", "task"])
# task/subtask are part of the same trace,
# and subtask is a child of task
self.assertSameTrace(spans[1], spans[2])
self.assertIsChildOf(spans[1], spans[2])
# initial task is not related in any way to those two tasks
self.assertNotSameTrace(spans[0], spans[1])
self.assertEqual(spans[0].parent, None)
self.assertEqual(spans[2].parent, None)
def task(self, span):
# Create a new Span for this task
with self.tracer.start_active_span("task"):
with self.tracer.scope_manager.activate(span, True):
# Simulate work strictly related to the initial Span
pass
# Use the task span as parent of a new subtask
with self.tracer.start_active_span("subtask"):
pass
def submit_another_task(self, span):
self.executor.submit(self.task, span)

View File

@ -1,19 +0,0 @@
Client-Server example.
======================
This example shows a ``Span`` created by a ``Client``, which will send a ``Message`` / ``SpanContext`` to a ``Server``, which will in turn extract such context and use it as parent of a new (server-side) ``Span``.
``Client.send()`` is used to send messages and inject the ``SpanContext`` using the ``TEXT_MAP`` format, and ``Server.process()`` will process received messages and will extract the context used as parent.
.. code-block:: python
def send(self):
with self.tracer.start_active_span("send") as scope:
scope.span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
message = {}
self.tracer.inject(scope.span.context,
opentracing.Format.TEXT_MAP,
message)
self.queue.put(message)

View File

@ -1,79 +0,0 @@
from __future__ import print_function
import asyncio
import opentracing
from opentracing.ext import tags
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import get_logger, get_one_by_tag, stop_loop_when
logger = get_logger(__name__)
class Server:
def __init__(self, *args, **kwargs):
tracer = kwargs.pop("tracer")
queue = kwargs.pop("queue")
super(Server, self).__init__(*args, **kwargs)
self.tracer = tracer
self.queue = queue
async def run(self):
value = await self.queue.get()
self.process(value)
def process(self, message):
logger.info("Processing message in server")
ctx = self.tracer.extract(opentracing.Format.TEXT_MAP, message)
with self.tracer.start_active_span("receive", child_of=ctx) as scope:
scope.span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_SERVER)
class Client:
def __init__(self, tracer, queue):
self.tracer = tracer
self.queue = queue
async def send(self):
with self.tracer.start_active_span("send") as scope:
scope.span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
message = {}
self.tracer.inject(
scope.span.context, opentracing.Format.TEXT_MAP, message
)
await self.queue.put(message)
logger.info("Sent message from client")
class TestAsyncio(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.queue = asyncio.Queue()
self.loop = asyncio.get_event_loop()
self.server = Server(tracer=self.tracer, queue=self.queue)
def test(self):
client = Client(self.tracer, self.queue)
self.loop.create_task(self.server.run())
self.loop.create_task(client.send())
stop_loop_when(
self.loop,
lambda: len(self.tracer.finished_spans()) >= 2,
timeout=5.0,
)
self.loop.run_forever()
spans = self.tracer.finished_spans()
self.assertIsNotNone(
get_one_by_tag(spans, tags.SPAN_KIND, tags.SPAN_KIND_RPC_SERVER)
)
self.assertIsNotNone(
get_one_by_tag(spans, tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
)

View File

@ -1,75 +0,0 @@
from __future__ import print_function
from queue import Queue
from threading import Thread
import opentracing
from opentracing.ext import tags
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import await_until, get_logger, get_one_by_tag
logger = get_logger(__name__)
class Server(Thread):
def __init__(self, *args, **kwargs):
tracer = kwargs.pop("tracer")
queue = kwargs.pop("queue")
super(Server, self).__init__(*args, **kwargs)
self.daemon = True
self.tracer = tracer
self.queue = queue
def run(self):
value = self.queue.get()
self.process(value)
def process(self, message):
logger.info("Processing message in server")
ctx = self.tracer.extract(opentracing.Format.TEXT_MAP, message)
with self.tracer.start_active_span("receive", child_of=ctx) as scope:
scope.span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_SERVER)
class Client:
def __init__(self, tracer, queue):
self.tracer = tracer
self.queue = queue
def send(self):
with self.tracer.start_active_span("send") as scope:
scope.span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
message = {}
self.tracer.inject(
scope.span.context, opentracing.Format.TEXT_MAP, message
)
self.queue.put(message)
logger.info("Sent message from client")
class TestThreads(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.queue = Queue()
self.server = Server(tracer=self.tracer, queue=self.queue)
self.server.start()
def test(self):
client = Client(self.tracer, self.queue)
client.send()
await_until(lambda: len(self.tracer.finished_spans()) >= 2)
spans = self.tracer.finished_spans()
self.assertIsNotNone(
get_one_by_tag(spans, tags.SPAN_KIND, tags.SPAN_KIND_RPC_SERVER)
)
self.assertIsNotNone(
get_one_by_tag(spans, tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
)

View File

@ -1,23 +0,0 @@
Common Request Handler example.
===============================
This example shows a ``Span`` used with ``RequestHandler``, which is used as a middleware (as in web frameworks) to manage a new ``Span`` per operation through its ``before_request()`` / ``after_response()`` methods.
Implementation details:
* For ``threading``, no active ``Span`` is consumed as the tasks may be run concurrently on different threads, and an explicit ``SpanContext`` has to be saved to be used as parent.
RequestHandler implementation:
.. code-block:: python
def before_request(self, request, request_context):
# If we should ignore the active Span, use any passed SpanContext
# as the parent. Else, use the active one.
span = self.tracer.start_span("send",
child_of=self.context,
ignore_active_span=True)

View File

@ -1,38 +0,0 @@
from __future__ import print_function
from opentracing.ext import tags
from ..utils import get_logger
logger = get_logger(__name__)
class RequestHandler:
def __init__(self, tracer, context=None, ignore_active_span=True):
self.tracer = tracer
self.context = context
self.ignore_active_span = ignore_active_span
def before_request(self, request, request_context):
logger.info("Before request %s", request)
# If we should ignore the active Span, use any passed SpanContext
# as the parent. Else, use the active one.
if self.ignore_active_span:
span = self.tracer.start_span(
"send", child_of=self.context, ignore_active_span=True
)
else:
span = self.tracer.start_span("send")
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
request_context["span"] = span
def after_request(self, request, request_context):
# pylint: disable=no-self-use
logger.info("After request %s", request)
span = request_context.get("span")
if span is not None:
span.finish()

View File

@ -1,136 +0,0 @@
from __future__ import print_function
import asyncio
from opentracing.ext import tags
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import get_logger, get_one_by_operation_name, stop_loop_when
from .request_handler import RequestHandler
logger = get_logger(__name__)
class Client:
def __init__(self, request_handler, loop):
self.request_handler = request_handler
self.loop = loop
async def send_task(self, message):
request_context = {}
async def before_handler():
self.request_handler.before_request(message, request_context)
async def after_handler():
self.request_handler.after_request(message, request_context)
await before_handler()
await after_handler()
return "%s::response" % message
def send(self, message):
return self.send_task(message)
def send_sync(self, message):
return self.loop.run_until_complete(self.send_task(message))
class TestAsyncio(OpenTelemetryTestCase):
"""
There is only one instance of 'RequestHandler' per 'Client'. Methods of
'RequestHandler' are executed in different Tasks, and no Span propagation
among them is done automatically.
Therefore we cannot use current active span and activate span.
So one issue here is setting correct parent span.
"""
def setUp(self):
self.tracer = MockTracer()
self.loop = asyncio.get_event_loop()
self.client = Client(RequestHandler(self.tracer), self.loop)
def test_two_callbacks(self):
res_future1 = self.loop.create_task(self.client.send("message1"))
res_future2 = self.loop.create_task(self.client.send("message2"))
stop_loop_when(
self.loop,
lambda: len(self.tracer.finished_spans()) >= 2,
timeout=5.0,
)
self.loop.run_forever()
self.assertEqual("message1::response", res_future1.result())
self.assertEqual("message2::response", res_future2.result())
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 2)
for span in spans:
self.assertEqual(
span.attributes.get(tags.SPAN_KIND, None),
tags.SPAN_KIND_RPC_CLIENT,
)
self.assertNotSameTrace(spans[0], spans[1])
self.assertIsNone(spans[0].parent)
self.assertIsNone(spans[1].parent)
def test_parent_not_picked(self):
"""Active parent should not be picked up by child."""
async def do_task():
with self.tracer.start_active_span("parent"):
response = await self.client.send_task("no_parent")
self.assertEqual("no_parent::response", response)
self.loop.run_until_complete(do_task())
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 2)
child_span = get_one_by_operation_name(spans, "send")
self.assertIsNotNone(child_span)
parent_span = get_one_by_operation_name(spans, "parent")
self.assertIsNotNone(parent_span)
# Here check that there is no parent-child relation.
self.assertIsNotChildOf(child_span, parent_span)
def test_good_solution_to_set_parent(self):
"""Asyncio and contextvars are integrated, in this case it is not needed
to activate current span by hand.
"""
async def do_task():
with self.tracer.start_active_span("parent"):
# Set ignore_active_span to False indicating that the
# framework will do it for us.
req_handler = RequestHandler(
self.tracer, ignore_active_span=False,
)
client = Client(req_handler, self.loop)
response = await client.send_task("correct_parent")
self.assertEqual("correct_parent::response", response)
# Send second request, now there is no active parent,
# but it will be set, ups
response = await client.send_task("wrong_parent")
self.assertEqual("wrong_parent::response", response)
self.loop.run_until_complete(do_task())
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 3)
spans = sorted(spans, key=lambda x: x.start_time)
parent_span = get_one_by_operation_name(spans, "parent")
self.assertIsNotNone(parent_span)
self.assertIsChildOf(spans[1], parent_span)
self.assertIsNotChildOf(spans[2], parent_span)

View File

@ -1,119 +0,0 @@
from __future__ import print_function
from concurrent.futures import ThreadPoolExecutor
from opentracing.ext import tags
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import get_logger, get_one_by_operation_name
from .request_handler import RequestHandler
logger = get_logger(__name__)
class Client:
def __init__(self, request_handler, executor):
self.request_handler = request_handler
self.executor = executor
def send_task(self, message):
request_context = {}
def before_handler():
self.request_handler.before_request(message, request_context)
def after_handler():
self.request_handler.after_request(message, request_context)
self.executor.submit(before_handler).result()
self.executor.submit(after_handler).result()
return "%s::response" % message
def send(self, message):
return self.executor.submit(self.send_task, message)
def send_sync(self, message, timeout=5.0):
fut = self.executor.submit(self.send_task, message)
return fut.result(timeout=timeout)
class TestThreads(OpenTelemetryTestCase):
"""
There is only one instance of 'RequestHandler' per 'Client'. Methods of
'RequestHandler' are executed concurrently in different threads which are
reused (executor). Therefore we cannot use current active span and
activate span. So one issue here is setting correct parent span.
"""
def setUp(self):
self.tracer = MockTracer()
self.executor = ThreadPoolExecutor(max_workers=3)
self.client = Client(RequestHandler(self.tracer), self.executor)
def test_two_callbacks(self):
response_future1 = self.client.send("message1")
response_future2 = self.client.send("message2")
self.assertEqual("message1::response", response_future1.result(5.0))
self.assertEqual("message2::response", response_future2.result(5.0))
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 2)
for span in spans:
self.assertEqual(
span.attributes.get(tags.SPAN_KIND, None),
tags.SPAN_KIND_RPC_CLIENT,
)
self.assertNotSameTrace(spans[0], spans[1])
self.assertIsNone(spans[0].parent)
self.assertIsNone(spans[1].parent)
def test_parent_not_picked(self):
"""Active parent should not be picked up by child."""
with self.tracer.start_active_span("parent"):
response = self.client.send_sync("no_parent")
self.assertEqual("no_parent::response", response)
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 2)
child_span = get_one_by_operation_name(spans, "send")
self.assertIsNotNone(child_span)
parent_span = get_one_by_operation_name(spans, "parent")
self.assertIsNotNone(parent_span)
# Here check that there is no parent-child relation.
self.assertIsNotChildOf(child_span, parent_span)
def test_bad_solution_to_set_parent(self):
"""Solution is bad because parent is per client and is not automatically
activated depending on the context.
"""
with self.tracer.start_active_span("parent") as scope:
client = Client(
# Pass a span context to be used ad the parent.
RequestHandler(self.tracer, scope.span.context),
self.executor,
)
response = client.send_sync("correct_parent")
self.assertEqual("correct_parent::response", response)
response = client.send_sync("wrong_parent")
self.assertEqual("wrong_parent::response", response)
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 3)
spans = sorted(spans, key=lambda x: x.start_time)
parent_span = get_one_by_operation_name(spans, "parent")
self.assertIsNotNone(parent_span)
self.assertIsChildOf(spans[1], parent_span)
self.assertIsChildOf(spans[2], parent_span)

View File

@ -1,18 +0,0 @@
Late Span finish example.
=========================
This example shows a ``Span`` for a top-level operation, with independent, unknown lifetime, acting as parent of a few asynchronous subtasks (which must re-activate it but not finish it).
.. code-block:: python
# Fire away a few subtasks, passing a parent Span whose lifetime
# is not tied at all to the children.
def submit_subtasks(self, parent_span):
def task(name, interval):
with self.tracer.scope_manager.activate(parent_span, False):
with self.tracer.start_active_span(name):
time.sleep(interval)
self.executor.submit(task, "task1", 0.1)
self.executor.submit(task, "task2", 0.3)

View File

@ -1,51 +0,0 @@
from __future__ import print_function
import asyncio
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import get_logger, stop_loop_when
logger = get_logger(__name__)
class TestAsyncio(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.loop = asyncio.get_event_loop()
def test_main(self):
# Create a Span and use it as (explicit) parent of a pair of subtasks.
parent_span = self.tracer.start_span("parent")
self.submit_subtasks(parent_span)
stop_loop_when(
self.loop,
lambda: len(self.tracer.finished_spans()) >= 2,
timeout=5.0,
)
self.loop.run_forever()
# Late-finish the parent Span now.
parent_span.finish()
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 3)
self.assertNamesEqual(spans, ["task1", "task2", "parent"])
for idx in range(2):
self.assertSameTrace(spans[idx], spans[-1])
self.assertIsChildOf(spans[idx], spans[-1])
self.assertTrue(spans[idx].end_time <= spans[-1].end_time)
# Fire away a few subtasks, passing a parent Span whose lifetime
# is not tied at all to the children.
def submit_subtasks(self, parent_span):
async def task(name):
logger.info("Running %s", name)
with self.tracer.scope_manager.activate(parent_span, False):
with self.tracer.start_active_span(name):
await asyncio.sleep(0.1)
self.loop.create_task(task("task1"))
self.loop.create_task(task("task2"))

View File

@ -1,44 +0,0 @@
from __future__ import print_function
import time
from concurrent.futures import ThreadPoolExecutor
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
class TestThreads(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.executor = ThreadPoolExecutor(max_workers=3)
def test_main(self):
# Create a Span and use it as (explicit) parent of a pair of subtasks.
parent_span = self.tracer.start_span("parent")
self.submit_subtasks(parent_span)
# Wait for the threadpool to be done.
self.executor.shutdown(True)
# Late-finish the parent Span now.
parent_span.finish()
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 3)
self.assertNamesEqual(spans, ["task1", "task2", "parent"])
for idx in range(2):
self.assertSameTrace(spans[idx], spans[-1])
self.assertIsChildOf(spans[idx], spans[-1])
self.assertTrue(spans[idx].end_time <= spans[-1].end_time)
# Fire away a few subtasks, passing a parent Span whose lifetime
# is not tied at all to the children.
def submit_subtasks(self, parent_span):
def task(name, interval):
with self.tracer.scope_manager.activate(parent_span, False):
with self.tracer.start_active_span(name):
time.sleep(interval)
self.executor.submit(task, "task1", 0.1)
self.executor.submit(task, "task2", 0.3)

View File

@ -1,19 +0,0 @@
Listener Response example.
==========================
This example shows a ``Span`` created upon a message being sent to a ``Client``, and its handling along a related, **not shared** ``ResponseListener`` object with a ``on_response(self, response)`` method to finish it.
.. code-block:: python
def _task(self, message, listener):
res = "%s::response" % message
listener.on_response(res)
return res
def send_sync(self, message):
span = self.tracer.start_span("send")
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
listener = ResponseListener(span)
return self.executor.submit(self._task, message, listener).result()

View File

@ -1,7 +0,0 @@
class ResponseListener:
def __init__(self, span):
self.span = span
def on_response(self, res):
del res
self.span.finish()

View File

@ -1,45 +0,0 @@
from __future__ import print_function
import asyncio
from opentracing.ext import tags
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import get_one_by_tag
from .response_listener import ResponseListener
class Client:
def __init__(self, tracer, loop):
self.tracer = tracer
self.loop = loop
async def task(self, message, listener):
res = "%s::response" % message
listener.on_response(res)
return res
def send_sync(self, message):
span = self.tracer.start_span("send")
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
listener = ResponseListener(span)
return self.loop.run_until_complete(self.task(message, listener))
class TestAsyncio(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.loop = asyncio.get_event_loop()
def test_main(self):
client = Client(self.tracer, self.loop)
res = client.send_sync("message")
self.assertEqual(res, "message::response")
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 1)
span = get_one_by_tag(spans, tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
self.assertIsNotNone(span)

View File

@ -1,45 +0,0 @@
from __future__ import print_function
from concurrent.futures import ThreadPoolExecutor
from opentracing.ext import tags
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import get_one_by_tag
from .response_listener import ResponseListener
class Client:
def __init__(self, tracer):
self.tracer = tracer
self.executor = ThreadPoolExecutor(max_workers=3)
def _task(self, message, listener):
# pylint: disable=no-self-use
res = "%s::response" % message
listener.on_response(res)
return res
def send_sync(self, message):
span = self.tracer.start_span("send")
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
listener = ResponseListener(span)
return self.executor.submit(self._task, message, listener).result()
class TestThreads(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
def test_main(self):
client = Client(self.tracer)
res = client.send_sync("message")
self.assertEqual(res, "message::response")
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 1)
span = get_one_by_tag(spans, tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
self.assertIsNotNone(span)

View File

@ -1,44 +0,0 @@
Multiple callbacks example.
===========================
This example shows a ``Span`` created for a top-level operation, covering a set of asynchronous operations (representing callbacks), and have this ``Span`` finished when **all** of them have been executed.
``Client.send()`` is used to create a new asynchronous operation (callback), and in turn every operation both restores the active ``Span``, and creates a child ``Span`` (useful for measuring the performance of each callback).
Implementation details:
* For ``threading``, a thread-safe counter is put in each ``Span`` to keep track of the pending callbacks, and call ``Span.finish()`` when the count becomes 0.
* For ``asyncio`` the children corotuines representing the subtasks are simply yielded over, so no counter is needed.
``threading`` implementation:
.. code-block:: python
def task(self, interval, parent_span):
logger.info("Starting task")
try:
scope = self.tracer.scope_manager.activate(parent_span, False)
with self.tracer.start_active_span("task"):
time.sleep(interval)
finally:
scope.close()
if parent_span._ref_count.decr() == 0:
parent_span.finish()
``asyncio`` implementation:
.. code-block:: python
async def task(self, interval, parent_span):
logger.info("Starting task")
with self.tracer.start_active_span("task"):
await asyncio.sleep(interval)
# Invoke and yield over the corotuines.
with self.tracer.start_active_span("parent"):
tasks = self.submit_callbacks()
await asyncio.gather(*tasks)

View File

@ -1,59 +0,0 @@
from __future__ import print_function
import asyncio
import random
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import get_logger, stop_loop_when
random.seed()
logger = get_logger(__name__)
class TestAsyncio(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.loop = asyncio.get_event_loop()
def test_main(self):
# Need to run within a Task, as the scope manager depends
# on Task.current_task()
async def main_task():
with self.tracer.start_active_span("parent"):
tasks = self.submit_callbacks()
await asyncio.gather(*tasks)
self.loop.create_task(main_task())
stop_loop_when(
self.loop,
lambda: len(self.tracer.finished_spans()) >= 4,
timeout=5.0,
)
self.loop.run_forever()
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 4)
self.assertNamesEqual(spans, ["task", "task", "task", "parent"])
for idx in range(3):
self.assertSameTrace(spans[idx], spans[-1])
self.assertIsChildOf(spans[idx], spans[-1])
async def task(self, interval, parent_span):
logger.info("Starting task")
with self.tracer.scope_manager.activate(parent_span, False):
with self.tracer.start_active_span("task"):
await asyncio.sleep(interval)
def submit_callbacks(self):
parent_span = self.tracer.scope_manager.active.span
tasks = []
for _ in range(3):
interval = 0.1 + random.randint(200, 500) * 0.001
task = self.loop.create_task(self.task(interval, parent_span))
tasks.append(task)
return tasks

View File

@ -1,59 +0,0 @@
from __future__ import print_function
import random
import time
from concurrent.futures import ThreadPoolExecutor
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import RefCount, get_logger
random.seed()
logger = get_logger(__name__)
class TestThreads(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.executor = ThreadPoolExecutor(max_workers=3)
def test_main(self):
try:
scope = self.tracer.start_active_span(
"parent", finish_on_close=False
)
scope.span.ref_count = RefCount(1)
self.submit_callbacks(scope.span)
finally:
scope.close()
if scope.span.ref_count.decr() == 0:
scope.span.finish()
self.executor.shutdown(True)
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 4)
self.assertNamesEqual(spans, ["task", "task", "task", "parent"])
for idx in range(3):
self.assertSameTrace(spans[idx], spans[-1])
self.assertIsChildOf(spans[idx], spans[-1])
def task(self, interval, parent_span):
logger.info("Starting task")
try:
scope = self.tracer.scope_manager.activate(parent_span, False)
with self.tracer.start_active_span("task"):
time.sleep(interval)
finally:
scope.close()
if parent_span.ref_count.decr() == 0:
parent_span.finish()
def submit_callbacks(self, parent_span):
for _ in range(3):
parent_span.ref_count.incr()
self.executor.submit(
self.task, 0.1 + random.randint(200, 500) * 0.001, parent_span
)

View File

@ -1,47 +0,0 @@
Nested callbacks example.
=========================
This example shows a ``Span`` for a top-level operation, and how it can be passed down on a list of nested callbacks (always one at a time), have it as the active one for each of them, and finished **only** when the last one executes. For Python, we have decided to do it in a **fire-and-forget** fashion.
Implementation details:
* For ``threading``, the ``Span`` is manually activatted it in each corotuine/task.
* For ``asyncio``, the active ``Span`` is not activated down the chain as the ``Context`` automatically propagates it.
``threading`` implementation:
.. code-block:: python
def submit(self):
span = self.tracer.scope_manager.active.span
def task1():
with self.tracer.scope_manager.activate(span, False):
span.set_tag("key1", "1")
def task2():
with self.tracer.scope_manager.activate(span, False):
span.set_tag("key2", "2")
...
``asyncio`` implementation:
.. code-block:: python
async def task1():
span.set_tag("key1", "1")
async def task2():
span.set_tag("key2", "2")
async def task3():
span.set_tag("key3", "3")
span.finish()
self.loop.create_task(task3())
self.loop.create_task(task2())
self.loop.create_task(task1())

View File

@ -1,57 +0,0 @@
from __future__ import print_function
import asyncio
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import stop_loop_when
class TestAsyncio(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.loop = asyncio.get_event_loop()
def test_main(self):
# Start a Span and let the callback-chain
# finish it when the task is done
async def task():
with self.tracer.start_active_span("one", finish_on_close=False):
self.submit()
self.loop.create_task(task())
stop_loop_when(
self.loop,
lambda: len(self.tracer.finished_spans()) == 1,
timeout=5.0,
)
self.loop.run_forever()
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 1)
self.assertEqual(spans[0].name, "one")
for idx in range(1, 4):
self.assertEqual(
spans[0].attributes.get("key%s" % idx, None), str(idx)
)
def submit(self):
span = self.tracer.scope_manager.active.span
async def task1():
span.set_tag("key1", "1")
async def task2():
span.set_tag("key2", "2")
async def task3():
span.set_tag("key3", "3")
span.finish()
self.loop.create_task(task3())
self.loop.create_task(task2())
self.loop.create_task(task1())

View File

@ -1,59 +0,0 @@
from __future__ import print_function
from concurrent.futures import ThreadPoolExecutor
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
from ..utils import await_until
class TestThreads(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.executor = ThreadPoolExecutor(max_workers=3)
def tearDown(self):
self.executor.shutdown(False)
def test_main(self):
# Start a Span and let the callback-chain
# finish it when the task is done
with self.tracer.start_active_span("one", finish_on_close=False):
self.submit()
# Cannot shutdown the executor and wait for the callbacks
# to be run, as in such case only the first will be executed,
# and the rest will get canceled.
await_until(lambda: len(self.tracer.finished_spans()) == 1, 5)
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 1)
self.assertEqual(spans[0].name, "one")
for idx in range(1, 4):
self.assertEqual(
spans[0].attributes.get("key%s" % idx, None), str(idx)
)
def submit(self):
span = self.tracer.scope_manager.active.span
def task1():
with self.tracer.scope_manager.activate(span, False):
span.set_tag("key1", "1")
def task2():
with self.tracer.scope_manager.activate(span, False):
span.set_tag("key2", "2")
def task3():
with self.tracer.scope_manager.activate(
span, True
):
span.set_tag("key3", "3")
self.executor.submit(task3)
self.executor.submit(task2)
self.executor.submit(task1)

View File

@ -1,42 +0,0 @@
Subtask Span propagation example.
=================================
This example shows an active ``Span`` being simply propagated to the subtasks -either threads or coroutines-, and finished **by** the parent task. In real-life scenarios instrumentation libraries may help with ``Span`` propagation **if** not offered by default (see implementation details below), but we show here the case without such help.
Implementation details:
* For ``threading``, the ``Span`` is manually passed down the call chain, activating it in each corotuine/task.
* For ``asyncio``, the active ``Span`` is not passed nor activated down the chain as the ``Context`` automatically propagates it.
``threading`` implementation:
.. code-block:: python
def parent_task(self, message):
with self.tracer.start_active_span("parent") as scope:
f = self.executor.submit(self.child_task, message, scope.span)
res = f.result()
return res
def child_task(self, message, span):
with self.tracer.scope_manager.activate(span, False):
with self.tracer.start_active_span("child"):
return "%s::response" % message
``asyncio`` implementation:
.. code-block:: python
async def parent_task(self, message): # noqa
with self.tracer.start_active_span("parent"):
res = await self.child_task(message)
return res
async def child_task(self, message):
# No need to pass/activate the parent Span, as it stays in the context.
with self.tracer.start_active_span("child"):
return "%s::response" % message

View File

@ -1,32 +0,0 @@
from __future__ import absolute_import, print_function
import asyncio
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
class TestAsyncio(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.loop = asyncio.get_event_loop()
def test_main(self):
res = self.loop.run_until_complete(self.parent_task("message"))
self.assertEqual(res, "message::response")
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 2)
self.assertNamesEqual(spans, ["child", "parent"])
self.assertIsChildOf(spans[0], spans[1])
async def parent_task(self, message): # noqa
with self.tracer.start_active_span("parent"):
res = await self.child_task(message)
return res
async def child_task(self, message):
# No need to pass/activate the parent Span, as it stays in the context.
with self.tracer.start_active_span("child"):
return "%s::response" % message

View File

@ -1,33 +0,0 @@
from __future__ import absolute_import, print_function
from concurrent.futures import ThreadPoolExecutor
from ..otel_ot_shim_tracer import MockTracer
from ..testcase import OpenTelemetryTestCase
class TestThreads(OpenTelemetryTestCase):
def setUp(self):
self.tracer = MockTracer()
self.executor = ThreadPoolExecutor(max_workers=3)
def test_main(self):
res = self.executor.submit(self.parent_task, "message").result()
self.assertEqual(res, "message::response")
spans = self.tracer.finished_spans()
self.assertEqual(len(spans), 2)
self.assertNamesEqual(spans, ["child", "parent"])
self.assertIsChildOf(spans[0], spans[1])
def parent_task(self, message):
with self.tracer.start_active_span("parent") as scope:
fut = self.executor.submit(self.child_task, message, scope.span)
res = fut.result()
return res
def child_task(self, message, span):
with self.tracer.scope_manager.activate(span, False):
with self.tracer.start_active_span("child"):
return "%s::response" % message

View File

@ -1,46 +0,0 @@
import unittest
import opentelemetry.trace as trace_api
# pylint: disable=C0103
class OpenTelemetryTestCase(unittest.TestCase):
def assertSameTrace(self, spanA, spanB):
return self.assertEqual(spanA.context.trace_id, spanB.context.trace_id)
def assertNotSameTrace(self, spanA, spanB):
return self.assertNotEqual(
spanA.context.trace_id, spanB.context.trace_id
)
def assertIsChildOf(self, spanA, spanB):
# spanA is child of spanB
self.assertIsNotNone(spanA.parent)
ctxA = spanA.parent
if isinstance(spanA.parent, trace_api.Span):
ctxA = spanA.parent.context
ctxB = spanB
if isinstance(ctxB, trace_api.Span):
ctxB = spanB.context
return self.assertEqual(ctxA.span_id, ctxB.span_id)
def assertIsNotChildOf(self, spanA, spanB):
# spanA is NOT child of spanB
if spanA.parent is None:
return
ctxA = spanA.parent
if isinstance(spanA.parent, trace_api.Span):
ctxA = spanA.parent.context
ctxB = spanB
if isinstance(ctxB, trace_api.Span):
ctxB = spanB.context
self.assertNotEqual(ctxA.span_id, ctxB.span_id)
def assertNamesEqual(self, spans, names):
self.assertEqual(list(map(lambda x: x.name, spans)), names)

View File

@ -1,78 +0,0 @@
from __future__ import print_function
import logging
import threading
import time
class RefCount:
"""Thread-safe counter"""
def __init__(self, count=1):
self._lock = threading.Lock()
self._count = count
def incr(self):
with self._lock:
self._count += 1
return self._count
def decr(self):
with self._lock:
self._count -= 1
return self._count
def await_until(func, timeout=5.0):
"""Polls for func() to return True"""
end_time = time.time() + timeout
while time.time() < end_time and not func():
time.sleep(0.01)
def stop_loop_when(loop, cond_func, timeout=5.0):
"""
Registers a periodic callback that stops the loop when cond_func() == True.
Compatible with both Tornado and asyncio.
"""
if cond_func() or timeout <= 0.0:
loop.stop()
return
timeout -= 0.1
loop.call_later(0.1, stop_loop_when, loop, cond_func, timeout)
def get_logger(name):
"""Returns a logger with log level set to INFO"""
logging.basicConfig(level=logging.INFO)
return logging.getLogger(name)
def get_one_by_tag(spans, key, value):
"""Return a single Span with a tag value/key from a list,
errors if more than one is found."""
found = []
for span in spans:
if span.attributes.get(key) == value:
found.append(span)
if len(found) > 1:
raise RuntimeError("Too many values")
return found[0] if len(found) > 0 else None
def get_one_by_operation_name(spans, name):
"""Return a single Span with a name from a list,
errors if more than one is found."""
found = []
for span in spans:
if span.name == name:
found.append(span)
if len(found) > 1:
raise RuntimeError("Too many values")
return found[0] if len(found) > 0 else None