Merge pull request #128 from NathanielRN/remove-opentracing-shim
Remove Opentracing Shim Instrumentation
This commit is contained in:
commit
5c9e043d69
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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/>`_
|
||||
|
|
@ -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
|
||||
|
|
@ -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__"])
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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"
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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))
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"))
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
class ResponseListener:
|
||||
def __init__(self, span):
|
||||
self.span = span
|
||||
|
||||
def on_response(self, res):
|
||||
del res
|
||||
self.span.finish()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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())
|
||||
|
|
@ -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())
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue