pax_global_header00006660000000000000000000000064137555232150014522gustar00rootroot0000000000000052 comment=98e106e793dbea922b72c214d300e2e6a1556134 opentracing-python-2.4.0/000077500000000000000000000000001375552321500153555ustar00rootroot00000000000000opentracing-python-2.4.0/.editorconfig000066400000000000000000000012241375552321500200310ustar00rootroot00000000000000# EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true # Matches multiple files with brace expansion notation # Set default charset [*.py] charset = utf-8 # 4 space indentation [*.py] indent_style = space indent_size = 4 # Tab indentation (no size specified) [Makefile] indent_style = tab # Matches the exact files either package.json or .travis.yml [{package.json,.travis.yml}] indent_style = space indent_size = 2 # Remove any whitespace characters preceding newline characters trim_trailing_whitespace = true opentracing-python-2.4.0/.gitignore000066400000000000000000000014711375552321500173500ustar00rootroot00000000000000*.py[cod] *.orig # Ignore gen/ directory where we temporarily store thrift classes to help with development gen/ # No local dbs *.db # C extensions *.so # Packages *.egg *.egg-info .tests bin build develop-eggs dist eggs parts sdist var .installed.cfg lib64 __pycache__ .cache/ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .coverage* .tox .noseids # Translations *.mo # Ignore python virtual environments env* thrift_env # Ignore local logs *.log logs/* !logs/.gitkeep # Ignore local log npm-debug.log # Ignore docs docs/_build/* # ignore ipython profile stuff config/profile_default/ !config/profile_default/ipython_config.py config/README protobuf/*.py .phutil_module_cache # Ignore coverage output *.xml coverage/ # Ignore benchmarks output perf.log perf.svg # vim *.swp .idea/ opentracing-python-2.4.0/.travis.yml000066400000000000000000000005501375552321500174660ustar00rootroot00000000000000dist: xenial language: python python: - "2.7" - "3.6" - "3.7" - "3.8" - "3.9" env: - TORNADO=">=4,<5" - TORNADO=">=5,<6" - TORNADO=">=6" matrix: allow_failures: - python: "3.8-dev" exclude: - python: "2.7" env: TORNADO=">=6" install: - make bootstrap - pip install -q "tornado$TORNADO" script: - make test testbed lint opentracing-python-2.4.0/CHANGELOG.rst000066400000000000000000000104561375552321500174040ustar00rootroot00000000000000.. :changelog: History ======= 2.4.0 (2020-11-19) ------------------ - Use current_task from asyncio module for Python 3.9 compatibility (#138) - Drop build support for Python 3.5 (#138) 2.3.0 (2020-01-02) ------------------ - Add AsyncioScopeManager based on contextvars and supporting Tornado 6 (#118) 2.2.0 (2019-05-10) ------------------ - Fix __exit__ method of Scope class (#120) - Add support for Python 3.5/3.7 and fix tests (#121) 2.1.0 (2019-04-27) ------------------ - Add support for indicating if a global tracer has been registered (#109) - Use pytest-cov==2.6.0 as 2.6.1 depends on pytest>=3.6.0 (#113) - Better error handling in context managers for Span/Scope. (#101) - Add log fields constants to opentracing.logs. (#99) - Move opentracing.ext.tags to opentracing.tags. (#103) - Add SERVICE tag (#100) - Fix unclosed active scope in tests (#97) - Initial implementation of a global Tracer. (#95) 2.0.0 (2018-07-10) ------------------ - Implement ScopeManager for in-process propagation. - Added a set of default ScopeManager implementations. - Added testbed/ for testing API changes. - Added MockTracer for instrumentation testing. 1.3.0 (2018-01-14) ------------------ - Added sphinx-generated documentation. - Remove 'futures' from install_requires (#62) - Add a harness check for unicode keys and vals (#40) - Have the harness try all tag value types (#39) 1.2.2 (2016-10-03) ------------------ - Fix KeyError when checking kwargs for optional values 1.2.1 (2016-09-22) ------------------ - Make Span.log(self, \**kwargs) smarter 1.2.0 (2016-09-21) ------------------ - Add Span.log_kv and deprecate older logging methods 1.1.0 (2016-08-06) ------------------ - Move set/get_baggage back to Span; add SpanContext.baggage - Raise exception on unknown format 2.0.0.dev3 (2016-07-26) ----------------------- - Support SpanContext 2.0.0.dev1 (2016-07-12) ----------------------- - Rename ChildOf/FollowsFrom to child_of/follows_from - Rename span_context to referee in Reference - Document expected behavior when referee=None 2.0.0.dev0 (2016-07-11) ----------------------- - Support SpanContext (and real semvers) 1.0rc4 (2016-05-21) ------------------- - Add standard tags per http://opentracing.io/data-semantics/ 1.0rc3 (2016-03-22) ------------------- - No changes yet 1.0rc3 (2016-03-22) ------------------- - Move to simpler carrier formats 1.0rc2 (2016-03-11) ------------------- - Remove the Injector/Extractor layer 1.0rc1 (2016-02-24) ------------------- - Upgrade to 1.0 RC specification 0.6.3 (2016-01-16) ------------------ - Rename repository back to opentracing-python 0.6.2 (2016-01-15) ------------------ - Validate chaining of logging calls 0.6.1 (2016-01-09) ------------------ - Fix typo in the attributes API test 0.6.0 (2016-01-09) ------------------ - Change inheritance to match api-go: TraceContextSource extends codecs, Tracer extends TraceContextSource - Create API harness 0.5.2 (2016-01-08) ------------------ - Update README and meta. 0.5.1 (2016-01-08) ------------------ - Prepare for PYPI publishing. 0.5.0 (2016-01-07) ------------------ - Remove debug flag - Allow passing tags to start methods - Add Span.add_tags() method 0.4.2 (2016-01-07) ------------------ - Add SPAN_KIND tag 0.4.0 (2016-01-06) ------------------ - Rename marshal -> encode 0.3.1 (2015-12-30) ------------------ - Fix std context implementation to refer to Trace Attributes instead of metadata 0.3.0 (2015-12-29) ------------------ - Rename trace tags to Trace Attributes. Rename RPC tags to PEER. Add README. 0.2.0 (2015-12-28) ------------------ - Export global `tracer` variable. 0.1.4 (2015-12-28) ------------------ - Rename RPC_SERVICE tag to make it symmetric 0.1.3 (2015-12-27) ------------------ - Allow repeated keys for span tags; add standard tag names for RPC 0.1.2 (2015-12-27) ------------------ - Move creation of child context to TraceContextSource 0.1.1 (2015-12-27) ------------------ - Add log methods 0.1.0 (2015-12-27) ------------------ - Initial public API opentracing-python-2.4.0/LICENSE000066400000000000000000000251311375552321500163640ustar00rootroot00000000000000 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 The OpenTracing 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. opentracing-python-2.4.0/MANIFEST.in000066400000000000000000000003111375552321500171060ustar00rootroot00000000000000recursive-include opentracing * recursive-include example *.py recursive-include example *.thrift recursive-include tests *.py prune testbed include * global-exclude *.pyc graft docs prune docs/_build opentracing-python-2.4.0/Makefile000066400000000000000000000050461375552321500170220ustar00rootroot00000000000000project := opentracing pytest := PYTHONDONTWRITEBYTECODE=1 py.test --tb short -rxs \ --cov-config .coveragerc --cov $(project) tests html_report := --cov-report=html test_args := --cov-report xml --cov-report term-missing .PHONY: clean-pyc clean-build docs clean testbed .DEFAULT_GOAL : help help: @echo "bootstrap - initialize local environement for development. Requires virtualenv." @echo "clean - remove all build, test, coverage and Python artifacts" @echo "clean-build - remove build artifacts" @echo "clean-pyc - remove Python file artifacts" @echo "clean-test - remove test and coverage artifacts" @echo "lint - check style with flake8" @echo "test - run tests quickly with the default Python" @echo "testbed - run testbed scenarios with the default Python" @echo "coverage - check code coverage quickly with the default Python" @echo "docs - generate Sphinx HTML documentation, including API docs" @echo "release - package and upload a release" @echo "dist - package" @echo "install - install the package to the active Python's site-packages" check-virtual-env: @echo virtual-env: $${VIRTUAL_ENV?"Please run in virtual-env"} bootstrap: check-virtual-env pip install -r requirements.txt pip install -r requirements-test.txt python setup.py develop clean: clean-build clean-pyc clean-test clean-build: rm -fr build/ rm -fr dist/ rm -fr .eggs/ find . -name '*.egg-info' -exec rm -fr {} + find . -name '*.egg' -exec rm -rf {} + clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + clean-test: rm -f .coverage rm -f coverage.xml rm -fr htmlcov/ lint: flake8 $(project) tests test: $(pytest) $(test_args) testbed: PYTHONDONTWRITEBYTECODE=1 python -m testbed jenkins: pip install -r requirements.txt pip install -r requirements-test.txt python setup.py develop CLAY_CONFIG=config/test.yaml $(pytest) $(test_args) --junit-xml=jenkins.xml coverage: coverage run --source $(project) setup.py test coverage report -m coverage html open htmlcov/index.html docs: pip show -q opentracing || python setup.py develop $(MAKE) -C docs clean $(MAKE) -C docs html release: clean @echo Please see README # python setup.py sdist upload # python setup.py bdist_wheel upload dist: clean @echo Please see README # python setup.py sdist # python setup.py bdist_wheel # ls -l dist install: pip install -r requirements.txt pip install -r requirements-test.txt echo skipping pip install -r requirements-doc.txt python setup.py install opentracing-python-2.4.0/README.rst000066400000000000000000000237301375552321500170510ustar00rootroot00000000000000OpenTracing API for Python ========================== |GitterChat| |BuildStatus| |PyPI| |ReadTheDocs| This library is a Python platform API for OpenTracing. Required Reading ---------------- In order to understand the Python platform API, one must first be familiar with the `OpenTracing project `_ and `terminology `_ more specifically. Status ------ In the current version, ``opentracing-python`` provides only the API and a basic no-op implementation that can be used by instrumentation libraries to collect and propagate distributed tracing context. Future versions will include a reference implementation utilizing an abstract Recorder interface, as well as a `Zipkin `_-compatible Tracer. Usage ----- The work of instrumentation libraries generally consists of three steps: 1. When a service receives a new request (over HTTP or some other protocol), it uses OpenTracing's inject/extract API to continue an active trace, creating a Span object in the process. If the request does not contain an active trace, the service starts a new trace and a new *root* Span. 2. The service needs to store the current Span in some request-local storage, (called ``Span`` *activation*) where it can be retrieved from when a child Span must be created, e.g. in case of the service making an RPC to another service. 3. When making outbound calls to another service, the current Span must be retrieved from request-local storage, a child span must be created (e.g., by using the ``start_child_span()`` helper), and that child span must be embedded into the outbound request (e.g., using HTTP headers) via OpenTracing's inject/extract API. Below are the code examples for the previously mentioned steps. Implementation of request-local storage needed for step 2 is specific to the service and/or frameworks / instrumentation libraries it is using, exposed as a ``ScopeManager`` child contained as ``Tracer.scope_manager``. See details below. Inbound request ^^^^^^^^^^^^^^^ Somewhere in your server's request handler code: .. code-block:: python def handle_request(request): span = before_request(request, opentracing.global_tracer()) # store span in some request-local storage using Tracer.scope_manager, # using the returned `Scope` as Context Manager to ensure # `Span` will be cleared and (in this case) `Span.finish()` be called. with tracer.scope_manager.activate(span, True) as scope: # actual business logic handle_request_for_real(request) def before_request(request, tracer): span_context = tracer.extract( format=Format.HTTP_HEADERS, carrier=request.headers, ) span = tracer.start_span( operation_name=request.operation, child_of=span_context) span.set_tag('http.url', request.full_url) remote_ip = request.remote_ip if remote_ip: span.set_tag(tags.PEER_HOST_IPV4, remote_ip) caller_name = request.caller_name if caller_name: span.set_tag(tags.PEER_SERVICE, caller_name) remote_port = request.remote_port if remote_port: span.set_tag(tags.PEER_PORT, remote_port) return span Outbound request ---------------- Somewhere in your service that's about to make an outgoing call: .. code-block:: python from opentracing import tags from opentracing.propagation import Format from opentracing_instrumentation import request_context # create and serialize a child span and use it as context manager with before_http_request( request=out_request, current_span_extractor=request_context.get_current_span): # actual call return urllib2.urlopen(request) def before_http_request(request, current_span_extractor): op = request.operation parent_span = current_span_extractor() outbound_span = opentracing.global_tracer().start_span( operation_name=op, child_of=parent_span ) outbound_span.set_tag('http.url', request.full_url) service_name = request.service_name host, port = request.host_port if service_name: outbound_span.set_tag(tags.PEER_SERVICE, service_name) if host: outbound_span.set_tag(tags.PEER_HOST_IPV4, host) if port: outbound_span.set_tag(tags.PEER_PORT, port) http_header_carrier = {} opentracing.global_tracer().inject( span_context=outbound_span, format=Format.HTTP_HEADERS, carrier=http_header_carrier) for key, value in http_header_carrier.iteritems(): request.add_header(key, value) return outbound_span Scope and within-process propagation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For getting/setting the current active ``Span`` in the used request-local storage, OpenTracing requires that every ``Tracer`` contains a ``ScopeManager`` that grants access to the active ``Span`` through a ``Scope``. Any ``Span`` may be transferred to another task or thread, but not ``Scope``. .. code-block:: python # Access to the active span is straightforward. scope = tracer.scope_manager.active() if scope is not None: scope.span.set_tag('...', '...') The common case starts a ``Scope`` that's automatically registered for intra-process propagation via ``ScopeManager``. Note that ``start_active_span('...')`` automatically finishes the span on ``Scope.close()`` (``start_active_span('...', finish_on_close=False)`` does not finish it, in contrast). .. code-block:: python # Manual activation of the Span. span = tracer.start_span(operation_name='someWork') with tracer.scope_manager.activate(span, True) as scope: # Do things. # Automatic activation of the Span. # finish_on_close is a required parameter. with tracer.start_active_span('someWork', finish_on_close=True) as scope: # Do things. # Handling done through a try construct: span = tracer.start_span(operation_name='someWork') scope = tracer.scope_manager.activate(span, True) try: # Do things. except Exception as e: span.set_tag('error', '...') finally: scope.close() **If there is a Scope, it will act as the parent to any newly started Span** unless the programmer passes ``ignore_active_span=True`` at ``start_span()``/``start_active_span()`` time or specified parent context explicitly: .. code-block:: python scope = tracer.start_active_span('someWork', ignore_active_span=True) Each service/framework ought to provide a specific ``ScopeManager`` implementation that relies on their own request-local storage (thread-local storage, or coroutine-based storage for asynchronous frameworks, for example). Scope managers ^^^^^^^^^^^^^^ This project includes a set of ``ScopeManager`` implementations under the ``opentracing.scope_managers`` submodule, which can be imported on demand: .. code-block:: python from opentracing.scope_managers import ThreadLocalScopeManager There exist implementations for ``thread-local`` (the default instance of the submodule ``opentracing.scope_managers``), ``gevent``, ``Tornado``, ``asyncio`` and ``contextvars``: .. code-block:: python from opentracing.scope_managers.gevent import GeventScopeManager # requires gevent from opentracing.scope_managers.tornado import TornadoScopeManager # requires tornado<6 from opentracing.scope_managers.asyncio import AsyncioScopeManager # fits for old asyncio applications, requires Python 3.4 or newer. from opentracing.scope_managers.contextvars import ContextVarsScopeManager # for asyncio applications, requires Python 3.7 or newer. **Note** that for asyncio applications it's preferable to use ``ContextVarsScopeManager`` instead of ``AsyncioScopeManager`` because of automatic parent span propagation to children coroutines, tasks or scheduled callbacks. Development ----------- Tests ^^^^^ .. code-block:: sh virtualenv env . ./env/bin/activate make bootstrap make test You can use `tox `_ to run tests as well. .. code-block:: sh tox Testbed suite ^^^^^^^^^^^^^ A testbed suite designed to test API changes and experimental features is included under the *testbed* directory. For more information, see the `Testbed README `_. Instrumentation Tests --------------------- This project has a working design of interfaces for the OpenTracing API. There is a MockTracer to facilitate unit-testing of OpenTracing Python instrumentation. .. code-block:: python from opentracing.mocktracer import MockTracer tracer = MockTracer() with tracer.start_span('someWork') as span: pass spans = tracer.finished_spans() someWorkSpan = spans[0] Documentation ^^^^^^^^^^^^^ .. code-block:: sh virtualenv env . ./env/bin/activate make bootstrap make docs The documentation is written to *docs/_build/html*. LICENSE ^^^^^^^ `Apache 2.0 License <./LICENSE>`__. Releases ^^^^^^^^ Before new release, add a summary of changes since last version to CHANGELOG.rst .. code-block:: sh pip install zest.releaser[recommended] prerelease release git push origin master --follow-tags python setup.py sdist upload -r pypi upload_docs -r pypi postrelease git push .. |GitterChat| image:: http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg :target: https://gitter.im/opentracing/public .. |BuildStatus| image:: https://travis-ci.org/opentracing/opentracing-python.svg?branch-master :target: https://travis-ci.org/opentracing/opentracing-python .. |PyPI| image:: https://badge.fury.io/py/opentracing.svg :target: https://badge.fury.io/py/opentracing .. |ReadTheDocs| image:: http://readthedocs.org/projects/opentracing-python/badge/?version=latest :target: https://opentracing-python.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status opentracing-python-2.4.0/docs/000077500000000000000000000000001375552321500163055ustar00rootroot00000000000000opentracing-python-2.4.0/docs/Makefile000066400000000000000000000011471375552321500177500ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = opentracing-python SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)opentracing-python-2.4.0/docs/api.rst000066400000000000000000000026571375552321500176220ustar00rootroot00000000000000Python API ========== Classes ------- .. autoclass:: opentracing.Span :members: .. autoclass:: opentracing.SpanContext :members: .. autoclass:: opentracing.Scope :members: .. autoclass:: opentracing.ScopeManager :members: .. autoclass:: opentracing.Tracer :members: .. autoclass:: opentracing.ReferenceType :members: .. autoclass:: opentracing.Reference :members: .. autoclass:: opentracing.Format :members: Utility Functions ----------------- .. autofunction:: opentracing.global_tracer .. autofunction:: opentracing.set_global_tracer .. autofunction:: opentracing.start_child_span .. autofunction:: opentracing.child_of .. autofunction:: opentracing.follows_from Exceptions ---------- .. autoclass:: opentracing.InvalidCarrierException :members: .. autoclass:: opentracing.SpanContextCorruptedException :members: .. autoclass:: opentracing.UnsupportedFormatException :members: MockTracer -------------- .. autoclass:: opentracing.mocktracer.MockTracer :members: Scope managers -------------- .. autoclass:: opentracing.scope_managers.ThreadLocalScopeManager :members: .. autoclass:: opentracing.scope_managers.gevent.GeventScopeManager :members: .. autoclass:: opentracing.scope_managers.tornado.TornadoScopeManager :members: .. autofunction:: opentracing.scope_managers.tornado.tracer_stack_context .. autoclass:: opentracing.scope_managers.asyncio.AsyncioScopeManager :members: opentracing-python-2.4.0/docs/changelog.rst000066400000000000000000000000361375552321500207650ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst opentracing-python-2.4.0/docs/conf.py000066400000000000000000000011171375552321500176040ustar00rootroot00000000000000import sphinx_rtd_theme project = 'opentracing-python' copyright = 'The OpenTracing Authors' author = 'The OpenTracing Authors' version = '1.2.3.dev0' release = '1.2' needs_sphinx = '1.0' extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] templates_path = [] source_suffix = '.rst' source_encoding = 'utf-8' master_doc = 'index' html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_static_path = [] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] intersphinx_mapping = { 'python': ('https://docs.python.org/', None), } opentracing-python-2.4.0/docs/index.rst000066400000000000000000000001121375552321500201400ustar00rootroot00000000000000.. include:: ../README.rst .. toctree:: :hidden: api changelog opentracing-python-2.4.0/docs/make.bat000066400000000000000000000014661375552321500177210ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build set SPHINXPROJ=opentracing-python if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd opentracing-python-2.4.0/opentracing/000077500000000000000000000000001375552321500176665ustar00rootroot00000000000000opentracing-python-2.4.0/opentracing/__init__.py000066400000000000000000000051101375552321500217740ustar00rootroot00000000000000# Copyright The OpenTracing Authors # Copyright Uber Technologies, Inc # # 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. from __future__ import absolute_import from .span import Span # noqa from .span import SpanContext # noqa from .scope import Scope # noqa from .scope_manager import ScopeManager # noqa from .tracer import child_of # noqa from .tracer import follows_from # noqa from .tracer import Reference # noqa from .tracer import ReferenceType # noqa from .tracer import Tracer # noqa from .tracer import start_child_span # noqa from .propagation import Format # noqa from .propagation import InvalidCarrierException # noqa from .propagation import SpanContextCorruptedException # noqa from .propagation import UnsupportedFormatException # noqa # Global variable that should be initialized to an instance of real tracer. # Note: it should be accessed via 'opentracing.tracer', not via # 'from opentracing import tracer', the latter seems to take a copy. # DEPRECATED, use global_tracer() and set_global_tracer() instead. tracer = Tracer() is_tracer_registered = False def global_tracer(): """Returns the global tracer. The default value is an instance of :class:`opentracing.Tracer` :rtype: :class:`Tracer` :return: The global tracer instance. """ return tracer def set_global_tracer(value): """Sets the global tracer. It is an error to pass ``None``. :param value: the :class:`Tracer` used as global instance. :type value: :class:`Tracer` """ if value is None: raise ValueError('The global Tracer tracer cannot be None') global tracer, is_tracer_registered tracer = value is_tracer_registered = True def is_global_tracer_registered(): """Indicates if a global tracer has been registered. :rtype: :value:bool :return: True if a global tracer has been registered, otherwise False. """ return is_tracer_registered def _reset_global_tracer(): """Reset any previously registered tracer. Intended for internal usage.""" global tracer, is_tracer_registered tracer = Tracer() is_tracer_registered = False opentracing-python-2.4.0/opentracing/ext/000077500000000000000000000000001375552321500204665ustar00rootroot00000000000000opentracing-python-2.4.0/opentracing/ext/__init__.py000066400000000000000000000000001375552321500225650ustar00rootroot00000000000000opentracing-python-2.4.0/opentracing/ext/tags.py000066400000000000000000000013361375552321500220010ustar00rootroot00000000000000# Copyright The OpenTracing Authors # Copyright Uber Technologies, Inc # # 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. from __future__ import absolute_import # DEPRECATED module, use opentracing.tags instead from ..tags import * # noqa opentracing-python-2.4.0/opentracing/harness/000077500000000000000000000000001375552321500213315ustar00rootroot00000000000000opentracing-python-2.4.0/opentracing/harness/__init__.py000066400000000000000000000000001375552321500234300ustar00rootroot00000000000000opentracing-python-2.4.0/opentracing/harness/api_check.py000066400000000000000000000304521375552321500236150ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import import mock import time import pytest import opentracing from opentracing import Format class APICompatibilityCheckMixin(object): """ A mixin class for validation that a given tracer implementation satisfies the requirements of the OpenTracing API. """ def tracer(self): raise NotImplementedError('Subclass must implement tracer()') def check_baggage_values(self): """If true, the test will validate Baggage items by storing and retrieving them from the trace context. If false, it will only attempt to store and retrieve the Baggage items to check the API compliance, but not actually validate stored values. The latter mode is only useful for no-op tracer. """ return True def check_scope_manager(self): """If true, the test suite will validate the `ScopeManager` propagation to ensure correct parenting. If false, it will only use the API without asserting. The latter mode is only useful for no-op tracer. """ return True def is_parent(self, parent, span): """Utility method that must be defined by Tracer implementers to define how the test suite can check when a `Span` is a parent of another one. It depends by the underlying implementation that is not part of the OpenTracing API. """ return False def test_active_span(self): tracer = self.tracer() span = tracer.start_span('Fry') if self.check_scope_manager(): assert tracer.active_span is None assert tracer.scope_manager.active is None with tracer.scope_manager.activate(span, True): assert tracer.active_span is span assert tracer.scope_manager.active.span is span def test_start_active_span(self): # the first usage returns a `Scope` that wraps a root `Span` tracer = self.tracer() with tracer.start_active_span('Fry') as scope: assert scope.span is not None if self.check_scope_manager(): assert self.is_parent(None, scope.span) def test_start_active_span_parent(self): # ensure the `ScopeManager` provides the right parenting tracer = self.tracer() with tracer.start_active_span('Fry') as parent: with tracer.start_active_span('Farnsworth') as child: if self.check_scope_manager(): assert self.is_parent(parent.span, child.span) def test_start_active_span_ignore_active_span(self): # ensure the `ScopeManager` ignores the active `Scope` # if the flag is set tracer = self.tracer() with tracer.start_active_span('Fry') as parent: with tracer.start_active_span('Farnsworth', ignore_active_span=True) as child: if self.check_scope_manager(): assert not self.is_parent(parent.span, child.span) def test_start_active_span_not_finish_on_close(self): # ensure a `Span` is finished when the `Scope` close tracer = self.tracer() scope = tracer.start_active_span('Fry', finish_on_close=False) with mock.patch.object(scope.span, 'finish') as finish: scope.close() assert finish.call_count == 0 def test_start_active_span_finish_on_close(self): # a `Span` is not finished when the flag is set tracer = self.tracer() scope = tracer.start_active_span('Fry', finish_on_close=True) with mock.patch.object(scope.span, 'finish') as finish: scope.close() if self.check_scope_manager(): assert finish.call_count == 1 def test_start_active_span_default_finish_on_close(self): # a `Span` is finished when no flag is set tracer = self.tracer() scope = tracer.start_active_span('Fry') with mock.patch.object(scope.span, 'finish') as finish: scope.close() if self.check_scope_manager(): assert finish.call_count == 1 def test_start_span(self): tracer = self.tracer() span = tracer.start_span(operation_name='Fry') span.finish() with tracer.start_span(operation_name='Fry', tags={'birthday': 'August 14 1974'}) as span: span.log_event('birthplace', payload={'hospital': 'Brooklyn Pre-Med Hospital', 'city': 'Old New York'}) def test_start_span_propagation(self): # `start_span` must inherit the current active `Scope` span tracer = self.tracer() with tracer.start_active_span('Fry') as parent: with tracer.start_span(operation_name='Farnsworth') as child: if self.check_scope_manager(): assert self.is_parent(parent.span, child) def test_start_span_propagation_ignore_active_span(self): # `start_span` doesn't inherit the current active `Scope` span # if the flag is set tracer = self.tracer() with tracer.start_active_span('Fry') as parent: with tracer.start_span(operation_name='Farnsworth', ignore_active_span=True) as child: if self.check_scope_manager(): assert not self.is_parent(parent.span, child) def test_start_span_with_parent(self): tracer = self.tracer() parent_span = tracer.start_span(operation_name='parent') assert parent_span is not None span = tracer.start_span( operation_name='Leela', child_of=parent_span) span.finish() span = tracer.start_span( operation_name='Leela', references=[opentracing.follows_from(parent_span.context)], tags={'birthplace': 'sewers'}) span.finish() parent_span.finish() def test_start_child_span(self): tracer = self.tracer() parent_span = tracer.start_span(operation_name='parent') assert parent_span is not None child_span = opentracing.start_child_span( parent_span, operation_name='Leela') child_span.finish() parent_span.finish() def test_set_operation_name(self): span = self.tracer().start_span().set_operation_name('Farnsworth') span.finish() def test_span_as_context_manager(self): tracer = self.tracer() finish = {'called': False} def mock_finish(*_): finish['called'] = True with tracer.start_span(operation_name='antiquing') as span: setattr(span, 'finish', mock_finish) assert finish['called'] is True # now try with exception finish['called'] = False try: with tracer.start_span(operation_name='antiquing') as span: setattr(span, 'finish', mock_finish) raise ValueError() except ValueError: assert finish['called'] is True else: raise AssertionError('Expected ValueError') # pragma: no cover def test_span_tag_value_types(self): with self.tracer().start_span(operation_name='ManyTypes') as span: span. \ set_tag('an_int', 9). \ set_tag('a_bool', True). \ set_tag('a_string', 'aoeuidhtns') def test_span_tags_with_chaining(self): span = self.tracer().start_span(operation_name='Farnsworth') span. \ set_tag('birthday', '9 April, 2841'). \ set_tag('loves', 'different lengths of wires') span. \ set_tag('unicode_val', u'non-ascii: \u200b'). \ set_tag(u'unicode_key_\u200b', 'ascii val') span.finish() def test_span_logs(self): span = self.tracer().start_span(operation_name='Fry') # Newer API span.log_kv( {'frozen.year': 1999, 'frozen.place': 'Cryogenics Labs'}) span.log_kv( {'defrosted.year': 2999, 'defrosted.place': 'Cryogenics Labs'}, time.time()) # Older API span.\ log_event('frozen', {'year': 1999, 'place': 'Cryogenics Labs'}). \ log_event('defrosted', {'year': 2999}). \ log_event('became his own grandfather', 1947) span.\ log(event='frozen'). \ log(payload={'year': 1999}). \ log(timestamp=time.time(), event='frozen', payload={'year': 1999}). \ log(timestamp=time.time(), event='unfrozen', payload={'year': 2999}) def test_span_baggage(self): with self.tracer().start_span(operation_name='Fry') as span: assert span.context.baggage == {} span_ref = span.set_baggage_item('Kiff-loves', 'Amy') assert span_ref is span val = span.get_baggage_item('Kiff-loves') if self.check_baggage_values(): assert 'Amy' == val pass def test_context_baggage(self): with self.tracer().start_span(operation_name='Fry') as span: assert span.context.baggage == {} span.set_baggage_item('Kiff-loves', 'Amy') if self.check_baggage_values(): assert span.context.baggage == {'Kiff-loves': 'Amy'} pass def test_text_propagation(self): with self.tracer().start_span(operation_name='Bender') as span: text_carrier = {} self.tracer().inject( span_context=span.context, format=opentracing.Format.TEXT_MAP, carrier=text_carrier) extracted_ctx = self.tracer().extract( format=opentracing.Format.TEXT_MAP, carrier=text_carrier) assert extracted_ctx.baggage == {} def test_binary_propagation(self): with self.tracer().start_span(operation_name='Bender') as span: bin_carrier = bytearray() self.tracer().inject( span_context=span.context, format=opentracing.Format.BINARY, carrier=bin_carrier) extracted_ctx = self.tracer().extract( format=opentracing.Format.BINARY, carrier=bin_carrier) assert extracted_ctx.baggage == {} def test_mandatory_formats(self): formats = [ (Format.TEXT_MAP, {}), (Format.HTTP_HEADERS, {}), (Format.BINARY, bytearray()), ] with self.tracer().start_span(operation_name='Bender') as span: for fmt, carrier in formats: # expecting no exceptions span.tracer.inject(span.context, fmt, carrier) span.tracer.extract(fmt, carrier) def test_unknown_format(self): custom_format = 'kiss my shiny metal ...' with self.tracer().start_span(operation_name='Bender') as span: with pytest.raises(opentracing.UnsupportedFormatException): span.tracer.inject(span.context, custom_format, {}) with pytest.raises(opentracing.UnsupportedFormatException): span.tracer.extract(custom_format, {}) def test_tracer_start_active_span_scope(self): # the Tracer ScopeManager should store the active Scope tracer = self.tracer() scope = tracer.start_active_span('Fry') if self.check_scope_manager(): assert tracer.scope_manager.active == scope scope.close() def test_tracer_start_span_scope(self): # the Tracer ScopeManager should not store the new Span tracer = self.tracer() span = tracer.start_span(operation_name='Fry') if self.check_scope_manager(): assert tracer.scope_manager.active is None span.finish() opentracing-python-2.4.0/opentracing/harness/scope_check.py000066400000000000000000000130701375552321500241520ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import mock from opentracing.span import Span class ScopeCompatibilityCheckMixin(object): """ A mixin class for validation that a given scope manager implementation satisfies the requirements of the OpenTracing API. """ def scope_manager(self): raise NotImplementedError('Subclass must implement scope_manager()') def run_test(self, test_fn): """ Utility method that can be optionally defined by ScopeManager implementers to run the passed test_fn() function in a given environment, such as a coroutine or greenlet. By default, it simply runs the passed test_fn() function in the current thread. """ test_fn() def test_missing_active_external(self): # test that 'active' does not fail outside the run_test() # implementation (greenlet or coroutine). scope_manager = self.scope_manager() assert scope_manager.active is None def test_missing_active(self): def fn(): scope_manager = self.scope_manager() assert scope_manager.active is None self.run_test(fn) def test_activate(self): def fn(): scope_manager = self.scope_manager() span = mock.MagicMock(spec=Span) scope = scope_manager.activate(span, False) assert scope is not None assert scope_manager.active is scope scope.close() assert span.finish.call_count == 0 assert scope_manager.active is None self.run_test(fn) def test_activate_external(self): # test that activate() does not fail outside the run_test() # implementation (greenlet or corotuine). scope_manager = self.scope_manager() span = mock.MagicMock(spec=Span) scope = scope_manager.activate(span, False) assert scope is not None assert scope_manager.active is scope scope.close() assert span.finish.call_count == 0 assert scope_manager.active is None def test_activate_finish_on_close(self): def fn(): scope_manager = self.scope_manager() span = mock.MagicMock(spec=Span) scope = scope_manager.activate(span, True) assert scope is not None assert scope_manager.active is scope scope.close() assert span.finish.call_count == 1 assert scope_manager.active is None self.run_test(fn) def test_activate_nested(self): def fn(): # when a Scope is closed, the previous one must be re-activated. scope_manager = self.scope_manager() parent_span = mock.MagicMock(spec=Span) child_span = mock.MagicMock(spec=Span) with scope_manager.activate(parent_span, True) as parent: assert parent is not None assert scope_manager.active is parent with scope_manager.activate(child_span, True) as child: assert child is not None assert scope_manager.active is child assert scope_manager.active is parent assert parent_span.finish.call_count == 1 assert child_span.finish.call_count == 1 assert scope_manager.active is None self.run_test(fn) def test_activate_finish_on_close_nested(self): def fn(): # finish_on_close must be correctly handled scope_manager = self.scope_manager() parent_span = mock.MagicMock(spec=Span) child_span = mock.MagicMock(spec=Span) parent = scope_manager.activate(parent_span, False) with scope_manager.activate(child_span, True): pass parent.close() assert parent_span.finish.call_count == 0 assert child_span.finish.call_count == 1 assert scope_manager.active is None self.run_test(fn) def test_close_wrong_order(self): def fn(): # only the active `Scope` can be closed scope_manager = self.scope_manager() parent_span = mock.MagicMock(spec=Span) child_span = mock.MagicMock(spec=Span) parent = scope_manager.activate(parent_span, True) child = scope_manager.activate(child_span, True) parent.close() assert parent_span.finish.call_count == 0 assert scope_manager.active == child self.run_test(fn) opentracing-python-2.4.0/opentracing/logs.py000066400000000000000000000046241375552321500212120ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import # The following log fields are described in greater detail at the # following url: # https://github.com/opentracing/specification/blob/master/semantic_conventions.md # --------------------------------------------------------------------------- # The type or "kind" of an error (only for event="error" logs). E.g., # "Exception", "OSError" # --------------------------------------------------------------------------- ERROR_KIND = 'error.kind' # --------------------------------------------------------------------------- # The actual Exception/Error object instance itself. E.g., A python # exceptions.NameError instance # --------------------------------------------------------------------------- ERROR_OBJECT = 'error.object' # --------------------------------------------------------------------------- # A stable identifier for some notable moment in the lifetime of a Span. # For instance, a mutex lock acquisition or release or the sorts of lifetime # events in a browser page load described in the Performance.timing # specification. E.g., from Zipkin, "cs", "sr", "ss", or "cr". Or, more # generally, "initialized" or "timed out". For errors, "error" # --------------------------------------------------------------------------- EVENT = 'event' # --------------------------------------------------------------------------- # A concise, human-readable, one-line message explaining the event. E.g., # "Could not connect to backend", "Cache invalidation succeeded" # --------------------------------------------------------------------------- MESSAGE = 'message' # --------------------------------------------------------------------------- # A stack trace in platform-conventional format; may or may not pertain to # an error. # --------------------------------------------------------------------------- STACK = 'stack' opentracing-python-2.4.0/opentracing/mocktracer/000077500000000000000000000000001375552321500220205ustar00rootroot00000000000000opentracing-python-2.4.0/opentracing/mocktracer/__init__.py000066400000000000000000000023051375552321500241310ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from .tracer import MockTracer # noqa from .propagator import Propagator # noqa opentracing-python-2.4.0/opentracing/mocktracer/binary_propagator.py000066400000000000000000000035221375552321500261160ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import pickle from .propagator import Propagator from opentracing import InvalidCarrierException, SpanContextCorruptedException class BinaryPropagator(Propagator): """A MockTracer Propagator for Format.BINARY.""" def inject(self, span_context, carrier): if type(carrier) is not bytearray: raise InvalidCarrierException() data = pickle.dumps(span_context) carrier.extend(data) def extract(self, carrier): if type(carrier) is not bytearray: raise InvalidCarrierException() try: span_context = pickle.loads(carrier) except (EOFError, pickle.PickleError): raise SpanContextCorruptedException() return span_context opentracing-python-2.4.0/opentracing/mocktracer/context.py000066400000000000000000000036021375552321500240570ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import opentracing class SpanContext(opentracing.SpanContext): """SpanContext satisfies the opentracing.SpanContext contract. trace_id and span_id are uint64's, so their range is [1, 2^64). """ def __init__( self, trace_id=None, span_id=None, baggage=None): self.trace_id = trace_id self.span_id = span_id self._baggage = baggage or opentracing.SpanContext.EMPTY_BAGGAGE @property def baggage(self): return self._baggage def with_baggage_item(self, key, value): new_baggage = self._baggage.copy() new_baggage[key] = value return SpanContext( trace_id=self.trace_id, span_id=self.span_id, baggage=new_baggage) opentracing-python-2.4.0/opentracing/mocktracer/propagator.py000066400000000000000000000023671375552321500245600ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import class Propagator(object): def inject(self, span_context, carrier): pass def extract(self, carrier): pass opentracing-python-2.4.0/opentracing/mocktracer/span.py000066400000000000000000000063421375552321500233400ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from threading import Lock import time from opentracing import Span class MockSpan(Span): """MockSpan is a thread-safe implementation of opentracing.Span. """ def __init__( self, tracer, operation_name=None, context=None, parent_id=None, tags=None, start_time=None): super(MockSpan, self).__init__(tracer, context) self._tracer = tracer self._lock = Lock() self.operation_name = operation_name self.start_time = start_time self.parent_id = parent_id self.tags = tags if tags is not None else {} self.finish_time = -1 self.finished = False self.logs = [] def set_operation_name(self, operation_name): with self._lock: self.operation_name = operation_name return super(MockSpan, self).set_operation_name(operation_name) def set_tag(self, key, value): with self._lock: if self.tags is None: self.tags = {} self.tags[key] = value return super(MockSpan, self).set_tag(key, value) def log_kv(self, key_values, timestamp=None): with self._lock: self.logs.append(LogData(key_values, timestamp)) return super(MockSpan, self).log_kv(key_values, timestamp) def finish(self, finish_time=None): with self._lock: finish_time = time.time() if finish_time is None else finish_time self.finish_time = finish_time self.finished = True self._tracer._append_finished_span(self) def set_baggage_item(self, key, value): new_context = self._context.with_baggage_item(key, value) with self._lock: self._context = new_context return self def get_baggage_item(self, key): with self._lock: return self.context.baggage.get(key) class LogData(object): def __init__( self, key_values, timestamp=None): self.key_values = key_values self.timestamp = time.time() if timestamp is None else timestamp opentracing-python-2.4.0/opentracing/mocktracer/text_propagator.py000066400000000000000000000050541375552321500256200ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from opentracing import SpanContextCorruptedException from .context import SpanContext from .propagator import Propagator prefix_tracer_state = 'ot-tracer-' prefix_baggage = 'ot-baggage-' field_name_trace_id = prefix_tracer_state + 'traceid' field_name_span_id = prefix_tracer_state + 'spanid' field_count = 2 class TextPropagator(Propagator): """A MockTracer Propagator for Format.TEXT_MAP.""" def inject(self, span_context, carrier): carrier[field_name_trace_id] = '{0:x}'.format(span_context.trace_id) carrier[field_name_span_id] = '{0:x}'.format(span_context.span_id) if span_context.baggage is not None: for k in span_context.baggage: carrier[prefix_baggage+k] = span_context.baggage[k] def extract(self, carrier): # noqa count = 0 span_id, trace_id = (0, 0) baggage = {} for k in carrier: v = carrier[k] k = k.lower() if k == field_name_span_id: span_id = int(v, 16) count += 1 elif k == field_name_trace_id: trace_id = int(v, 16) count += 1 elif k.startswith(prefix_baggage): baggage[k[len(prefix_baggage):]] = v if count != field_count: raise SpanContextCorruptedException() return SpanContext( span_id=span_id, trace_id=trace_id, baggage=baggage) opentracing-python-2.4.0/opentracing/mocktracer/tracer.py000066400000000000000000000155641375552321500236650ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from threading import Lock import time import opentracing from opentracing import Format, Tracer from opentracing import UnsupportedFormatException from opentracing.scope_managers import ThreadLocalScopeManager from .context import SpanContext from .span import MockSpan class MockTracer(Tracer): """MockTracer makes it easy to test the semantics of OpenTracing instrumentation. By using a MockTracer as a :class:`~opentracing.Tracer` implementation for tests, a developer can assert that :class:`~opentracing.Span` properties and relationships with other **Spans** are defined as expected by instrumentation code. By default, MockTracer registers propagators for :attr:`Format.TEXT_MAP`, :attr:`Format.HTTP_HEADERS` and :attr:`Format.BINARY`. The user should call :func:`register_propagator()` for each additional inject/extract format. """ def __init__(self, scope_manager=None): """Initialize a MockTracer instance.""" scope_manager = ThreadLocalScopeManager() \ if scope_manager is None else scope_manager super(MockTracer, self).__init__(scope_manager) self._propagators = {} self._finished_spans = [] self._spans_lock = Lock() # Simple-as-possible (consecutive for repeatability) id generation. self._next_id = 0 self._next_id_lock = Lock() self._register_required_propagators() def register_propagator(self, format, propagator): """Register a propagator with this MockTracer. :param string format: a :class:`~opentracing.Format` identifier like :attr:`~opentracing.Format.TEXT_MAP` :param **Propagator** propagator: a **Propagator** instance to handle inject/extract calls involving `format` """ self._propagators[format] = propagator def _register_required_propagators(self): from .text_propagator import TextPropagator from .binary_propagator import BinaryPropagator self.register_propagator(Format.TEXT_MAP, TextPropagator()) self.register_propagator(Format.HTTP_HEADERS, TextPropagator()) self.register_propagator(Format.BINARY, BinaryPropagator()) def finished_spans(self): """Return a copy of all finished **Spans** started by this MockTracer (since construction or the last call to :meth:`~MockTracer.reset()`) :rtype: list :return: a copy of the finished **Spans**. """ with self._spans_lock: return list(self._finished_spans) def reset(self): """Clear the finished **Spans** queue. Note that this does **not** have any effect on **Spans** created by MockTracer that have not finished yet; those will still be enqueued in :meth:`~MockTracer.finished_spans()` when they :func:`finish()`. """ with self._spans_lock: self._finished_spans = [] def _append_finished_span(self, span): with self._spans_lock: self._finished_spans.append(span) def _generate_id(self): with self._next_id_lock: self._next_id += 1 return self._next_id def start_active_span(self, operation_name, child_of=None, references=None, tags=None, start_time=None, ignore_active_span=False, finish_on_close=True): # create a new 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=None, child_of=None, references=None, tags=None, start_time=None, ignore_active_span=False): start_time = time.time() if start_time is None else start_time # See if we have a parent_ctx in `references` parent_ctx = None if child_of is not None: parent_ctx = ( child_of if isinstance(child_of, opentracing.SpanContext) else child_of.context) elif references is not None and len(references) > 0: # TODO only the first reference is currently used parent_ctx = references[0].referenced_context # retrieve the active SpanContext if not ignore_active_span and parent_ctx is None: scope = self.scope_manager.active if scope is not None: parent_ctx = scope.span.context # Assemble the child ctx ctx = SpanContext(span_id=self._generate_id()) if parent_ctx is not None: if parent_ctx._baggage is not None: ctx._baggage = parent_ctx._baggage.copy() ctx.trace_id = parent_ctx.trace_id else: ctx.trace_id = self._generate_id() # Tie it all together return MockSpan( self, operation_name=operation_name, context=ctx, parent_id=(None if parent_ctx is None else parent_ctx.span_id), tags=tags, start_time=start_time) def inject(self, span_context, format, carrier): if format in self._propagators: self._propagators[format].inject(span_context, carrier) else: raise UnsupportedFormatException() def extract(self, format, carrier): if format in self._propagators: return self._propagators[format].extract(carrier) else: raise UnsupportedFormatException() opentracing-python-2.4.0/opentracing/propagation.py000066400000000000000000000063361375552321500225730ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import class UnsupportedFormatException(Exception): """UnsupportedFormatException should be used when the provided format value is unknown or disallowed by the :class:`Tracer`. See :meth:`Tracer.inject()` and :meth:`Tracer.extract()`. """ pass class InvalidCarrierException(Exception): """InvalidCarrierException should be used when the provided carrier instance does not match what the `format` argument requires. See :meth:`Tracer.inject()` and :meth:`Tracer.extract()`. """ pass class SpanContextCorruptedException(Exception): """SpanContextCorruptedException should be used when the underlying :class:`SpanContext` state is seemingly present but not well-formed. See :meth:`Tracer.inject()` and :meth:`Tracer.extract()`. """ pass class Format(object): """A namespace for builtin carrier formats. These static constants are intended for use in the :meth:`Tracer.inject()` and :meth:`Tracer.extract()` methods. E.g.:: tracer.inject(span.context, Format.BINARY, binary_carrier) """ BINARY = 'binary' """ The BINARY format represents SpanContexts in an opaque bytearray carrier. For both :meth:`Tracer.inject()` and :meth:`Tracer.extract()` the carrier should be a bytearray instance. :meth:`Tracer.inject()` must append to the bytearray carrier (rather than replace its contents). """ TEXT_MAP = 'text_map' """ The TEXT_MAP format represents :class:`SpanContext`\\ s in a python ``dict`` mapping from strings to strings. Both the keys and the values have unrestricted character sets (unlike the HTTP_HEADERS format). NOTE: The TEXT_MAP carrier ``dict`` may contain unrelated data (e.g., arbitrary gRPC metadata). As such, the :class:`Tracer` implementation should use a prefix or other convention to distinguish tracer-specific key:value pairs. """ HTTP_HEADERS = 'http_headers' """ The HTTP_HEADERS format represents :class:`SpanContext`\\ s in a python ``dict`` mapping from character-restricted strings to strings. Keys and values in the HTTP_HEADERS carrier must be suitable for use as HTTP headers (without modification or further escaping). That is, the keys have a greatly restricted character set, casing for the keys may not be preserved by various intermediaries, and the values should be URL-escaped. NOTE: The HTTP_HEADERS carrier ``dict`` may contain unrelated data (e.g., arbitrary gRPC metadata). As such, the :class:`Tracer` implementation should use a prefix or other convention to distinguish tracer-specific key:value pairs. """ opentracing-python-2.4.0/opentracing/scope.py000066400000000000000000000063121375552321500213530ustar00rootroot00000000000000# Copyright (c) 2017-2019 The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from .span import Span class Scope(object): """A scope formalizes the activation and deactivation of a :class:`Span`, usually from a CPU standpoint. Many times a :class:`Span` will be extant (in that :meth:`Span.finish()` has not been called) despite being in a non-runnable state from a CPU/scheduler standpoint. For instance, a :class:`Span` representing the client side of an RPC will be unfinished but blocked on IO while the RPC is still outstanding. A scope defines when a given :class:`Span` is scheduled and on the path. :param manager: the :class:`ScopeManager` that created this :class:`Scope`. :type manager: ScopeManager :param span: the :class:`Span` used for this :class:`Scope`. :type span: Span """ def __init__(self, manager, span): """Initializes a scope for *span*.""" self._manager = manager self._span = span @property def span(self): """Returns the :class:`Span` wrapped by this :class:`Scope`. :rtype: Span """ return self._span @property def manager(self): """Returns the :class:`ScopeManager` that created this :class:`Scope`. :rtype: ScopeManager """ return self._manager def close(self): """Marks the end of the active period for this :class:`Scope`, updating :attr:`ScopeManager.active` in the process. NOTE: Calling this method more than once on a single :class:`Scope` leads to undefined behavior. """ pass def __enter__(self): """Allows :class:`Scope` to be used inside a Python Context Manager.""" return self def __exit__(self, exc_type, exc_val, exc_tb): """Calls :meth:`close()` when the execution is outside the Python Context Manager. If exception has occurred during execution, it is automatically logged and added as a tag to the :class:`Span`. :attr:`~operation.ext.tags.ERROR` will also be set to `True`. """ Span._on_error(self.span, exc_type, exc_val, exc_tb) self.close() opentracing-python-2.4.0/opentracing/scope_manager.py000066400000000000000000000054141375552321500230470ustar00rootroot00000000000000# Copyright (c) 2017 The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from .span import Span, SpanContext from .scope import Scope class ScopeManager(object): """The :class:`ScopeManager` interface abstracts both the activation of a :class:`Span` and access to an active :class:`Span`/:class:`Scope`. """ def __init__(self): # TODO: `tracer` should not be None, but we don't have a reference; # should we move the NOOP SpanContext, Span, Scope to somewhere # else so that they're globally reachable? self._noop_span = Span(tracer=None, context=SpanContext()) self._noop_scope = Scope(self, self._noop_span) def activate(self, span, finish_on_close): """Makes a :class:`Span` active. :param span: the :class:`Span` that should become active. :param finish_on_close: whether :class:`Span` should be automatically finished when :meth:`Scope.close()` is called. :rtype: Scope :return: a :class:`Scope` to control the end of the active period for *span*. It is a programming error to neglect to call :meth:`Scope.close()` on the returned instance. """ return self._noop_scope @property def active(self): """Returns the currently active :class:`Scope` which can be used to access the currently active :attr:`Scope.span`. If there is a non-null :class:`Scope`, its wrapped :class:`Span` becomes an implicit parent of any newly-created :class:`Span` at :meth:`Tracer.start_active_span()` time. :rtype: Scope :return: the :class:`Scope` that is active, or ``None`` if not available. """ return self._noop_scope opentracing-python-2.4.0/opentracing/scope_managers/000077500000000000000000000000001375552321500226545ustar00rootroot00000000000000opentracing-python-2.4.0/opentracing/scope_managers/__init__.py000066400000000000000000000057551375552321500250010ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import threading from opentracing import Scope, ScopeManager class ThreadLocalScopeManager(ScopeManager): """ :class:`~opentracing.ScopeManager` implementation that stores the current active :class:`~opentracing.Scope` using thread-local storage. """ def __init__(self): self._tls_scope = threading.local() def activate(self, span, finish_on_close): """ Make a :class:`~opentracing.Span` instance active. :param span: the :class:`~opentracing.Span` that should become active. :param finish_on_close: whether *span* should automatically be finished when :meth:`Scope.close()` is called. :return: a :class:`~opentracing.Scope` instance to control the end of the active period for the :class:`~opentracing.Span`. It is a programming error to neglect to call :meth:`Scope.close()` on the returned instance. """ scope = _ThreadLocalScope(self, span, finish_on_close) setattr(self._tls_scope, 'active', scope) return scope @property def active(self): """ Return the currently active :class:`~opentracing.Scope` which can be used to access the currently active :attr:`Scope.span`. :return: the :class:`~opentracing.Scope` that is active, or ``None`` if not available. """ return getattr(self._tls_scope, 'active', None) class _ThreadLocalScope(Scope): def __init__(self, manager, span, finish_on_close): super(_ThreadLocalScope, self).__init__(manager, span) self._finish_on_close = finish_on_close self._to_restore = manager.active def close(self): if self.manager.active is not self: return if self._finish_on_close: self.span.finish() setattr(self._manager._tls_scope, 'active', self._to_restore) opentracing-python-2.4.0/opentracing/scope_managers/asyncio.py000066400000000000000000000116751375552321500247050ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import asyncio from opentracing import Scope from opentracing.scope_managers import ThreadLocalScopeManager from .constants import ACTIVE_ATTR class AsyncioScopeManager(ThreadLocalScopeManager): """ :class:`~opentracing.ScopeManager` implementation for **asyncio** that stores the :class:`~opentracing.Scope` in the current :class:`Task` (:meth:`asyncio.current_task()`), falling back to thread-local storage if none was being executed. Automatic :class:`~opentracing.Span` propagation from parent coroutines to their children is not provided, which needs to be done manually: .. code-block:: python async def child_coroutine(span): # activate the parent Span, but do not finish it upon # deactivation. That will be done by the parent coroutine. with tracer.scope_manager.activate(span, finish_on_close=False): with tracer.start_active_span('child') as scope: ... async def parent_coroutine(): with tracer.start_active_span('parent') as scope: ... await child_coroutine(span) ... """ def activate(self, span, finish_on_close): """ Make a :class:`~opentracing.Span` instance active. :param span: the :class:`~opentracing.Span` that should become active. :param finish_on_close: whether *span* should automatically be finished when :meth:`Scope.close()` is called. If no :class:`Task` is being executed, thread-local storage will be used to store the :class:`~opentracing.Scope`. :return: a :class:`~opentracing.Scope` instance to control the end of the active period for the :class:`~opentracing.Span`. It is a programming error to neglect to call :meth:`Scope.close()` on the returned instance. """ task = self._get_task() if not task: return super(AsyncioScopeManager, self).activate(span, finish_on_close) scope = _AsyncioScope(self, span, finish_on_close) self._set_task_scope(scope, task) return scope @property def active(self): """ Return the currently active :class:`~opentracing.Scope` which can be used to access the currently active :attr:`Scope.span`. :return: the :class:`~opentracing.Scope` that is active, or ``None`` if not available. """ task = self._get_task() if not task: return super(AsyncioScopeManager, self).active return self._get_task_scope(task) def _get_task(self): try: # Prevent failure when run from a thread # without an event loop. loop = asyncio.get_event_loop() except RuntimeError: return None if hasattr(asyncio, 'current_task'): # Python 3.7+ return asyncio.current_task(loop=loop) else: # Python 3.6 and below return asyncio.Task.current_task(loop=loop) def _set_task_scope(self, scope, task=None): if task is None: task = self._get_task() setattr(task, ACTIVE_ATTR, scope) def _get_task_scope(self, task=None): if task is None: task = self._get_task() return getattr(task, ACTIVE_ATTR, None) class _AsyncioScope(Scope): def __init__(self, manager, span, finish_on_close): super(_AsyncioScope, self).__init__(manager, span) self._finish_on_close = finish_on_close self._to_restore = manager.active def close(self): if self.manager.active is not self: return self.manager._set_task_scope(self._to_restore) if self._finish_on_close: self.span.finish() opentracing-python-2.4.0/opentracing/scope_managers/constants.py000066400000000000000000000026561375552321500252530ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import # --------------------------------------------------------------------------- # ACTIVE_ATTR (string) is the name of the attribute storing the active Scope # in an external object, e.g. a coroutine or greenlet. # --------------------------------------------------------------------------- ACTIVE_ATTR = '__ot_active' opentracing-python-2.4.0/opentracing/scope_managers/contextvars.py000066400000000000000000000105511375552321500256100ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from contextlib import contextmanager from contextvars import ContextVar from opentracing import Scope, ScopeManager _SCOPE = ContextVar('scope') class ContextVarsScopeManager(ScopeManager): """ :class:`~opentracing.ScopeManager` implementation for **asyncio** that stores the :class:`~opentracing.Scope` using ContextVar. The scope manager provides automatic :class:`~opentracing.Span` propagation from parent coroutines, tasks and scheduled in event loop callbacks to their children. .. code-block:: python async def child_coroutine(): # No need manual activation of parent span in child coroutine. with tracer.start_active_span('child') as scope: ... async def parent_coroutine(): with tracer.start_active_span('parent') as scope: ... await child_coroutine() ... """ def activate(self, span, finish_on_close): """ Make a :class:`~opentracing.Span` instance active. :param span: the :class:`~opentracing.Span` that should become active. :param finish_on_close: whether *span* should automatically be finished when :meth:`Scope.close()` is called. :return: a :class:`~opentracing.Scope` instance to control the end of the active period for the :class:`~opentracing.Span`. It is a programming error to neglect to call :meth:`Scope.close()` on the returned instance. """ return self._set_scope(span, finish_on_close) @property def active(self): """ Return the currently active :class:`~opentracing.Scope` which can be used to access the currently active :attr:`Scope.span`. :return: the :class:`~opentracing.Scope` that is active, or ``None`` if not available. """ return self._get_scope() def _set_scope(self, span, finish_on_close): return _ContextVarsScope(self, span, finish_on_close) def _get_scope(self): return _SCOPE.get(None) class _ContextVarsScope(Scope): def __init__(self, manager, span, finish_on_close): super(_ContextVarsScope, self).__init__(manager, span) self._finish_on_close = finish_on_close self._token = _SCOPE.set(self) def close(self): if self.manager.active is not self: return _SCOPE.reset(self._token) if self._finish_on_close: self.span.finish() @contextmanager def no_parent_scope(): """ Context manager that resets current Scope. Intended to break span propagation to children coroutines, tasks or scheduled callbacks. .. code-block:: python from opentracing.scope_managers.contextvars import no_parent_scope def periodic() # `periodic` span will be children of root only at the first time. with self.tracer.start_active_span('periodic'): # Now we break span propagation. with no_parent_scope(): self.loop.call_soon(periodic) with self.tracer.start_active_span('root'): self.loop.call_soon(periodic) """ token = _SCOPE.set(None) try: yield finally: _SCOPE.reset(token) opentracing-python-2.4.0/opentracing/scope_managers/gevent.py000066400000000000000000000077331375552321500245300ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import gevent from opentracing import Scope, ScopeManager from .constants import ACTIVE_ATTR class GeventScopeManager(ScopeManager): """ :class:`~opentracing.ScopeManager` implementation for **gevent** that stores the :class:`~opentracing.Scope` in the current greenlet (:func:`gevent.getcurrent()`). Automatic :class:`~opentracing.Span` propagation from parent greenlets to their children is not provided, which needs to be done manually: .. code-block:: python def child_greenlet(span): # activate the parent Span, but do not finish it upon # deactivation. That will be done by the parent greenlet. with tracer.scope_manager.activate(span, finish_on_close=False): with tracer.start_active_span('child') as scope: ... def parent_greenlet(): with tracer.start_active_span('parent') as scope: ... gevent.spawn(child_greenlet, span).join() ... """ def activate(self, span, finish_on_close): """ Make a :class:`~opentracing.Span` instance active. :param span: the :class:`~opentracing.Span` that should become active. :param finish_on_close: whether *span* should automatically be finished when :meth:`Scope.close()` is called. :return: a :class:`~opentracing.Scope` instance to control the end of the active period for the :class:`~opentracing.Span`. It is a programming error to neglect to call :meth:`Scope.close()` on the returned instance. """ scope = _GeventScope(self, span, finish_on_close) self._set_greenlet_scope(scope) return scope @property def active(self): """ Return the currently active :class:`~opentracing.Scope` which can be used to access the currently active :attr:`Scope.span`. :return: the :class:`~opentracing.Scope` that is active, or ``None`` if not available. """ return self._get_greenlet_scope() def _get_greenlet_scope(self, greenlet=None): if greenlet is None: greenlet = gevent.getcurrent() return getattr(greenlet, ACTIVE_ATTR, None) def _set_greenlet_scope(self, scope, greenlet=None): if greenlet is None: greenlet = gevent.getcurrent() setattr(greenlet, ACTIVE_ATTR, scope) class _GeventScope(Scope): def __init__(self, manager, span, finish_on_close): super(_GeventScope, self).__init__(manager, span) self._finish_on_close = finish_on_close self._to_restore = manager.active def close(self): if self.manager.active is not self: return self.manager._set_greenlet_scope(self._to_restore) if self._finish_on_close: self.span.finish() opentracing-python-2.4.0/opentracing/scope_managers/tornado.py000066400000000000000000000241741375552321500247040ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import threading import tornado.stack_context from opentracing import Scope from opentracing.scope_managers import ThreadLocalScopeManager # Implementation based on # github.com/uber-common/opentracing-python-instrumentation/ class TornadoScopeManager(ThreadLocalScopeManager): """ :class:`~opentracing.ScopeManager` implementation for **Tornado** that stores the :class:`~opentracing.Scope` using a custom :class:`StackContext`, falling back to thread-local storage if none was found. Using it under :func:`tracer_stack_context()` will also automatically propagate the active :class:`~opentracing.Span` from parent coroutines to their children: .. code-block:: python @tornado.gen.coroutine def child_coroutine(): # No need to pass 'parent' and activate it here, # as it is automatically propagated. with tracer.start_active_span('child') as scope: ... @tornado.gen.coroutine def parent_coroutine(): with tracer.start_active_span('parent') as scope: ... yield child_coroutine() ... with tracer_stack_context(): loop.add_callback(parent_coroutine) .. note:: The current version does not support :class:`~opentracing.Span` activation in children coroutines when the parent yields over **multiple** of them, as the context is effectively shared by all, and the active :class:`~opentracing.Span` state is messed up: .. code-block:: python @tornado.gen.coroutine def coroutine(input): # No span should be activated here. # The parent Span will remain active, though. with tracer.start_span('child', child_of=tracer.active_span): ... @tornado.gen.coroutine def handle_request_wrapper(): res1 = coroutine('A') res2 = coroutine('B') yield [res1, res2] """ def activate(self, span, finish_on_close): """ Make a :class:`~opentracing.Span` instance active. :param span: the :class:`~opentracing.Span` that should become active. :param finish_on_close: whether *span* should automatically be finished when :meth:`Scope.close()` is called. If no :func:`tracer_stack_context()` is detected, thread-local storage will be used to store the :class:`~opentracing.Scope`. Observe that in this case the active :class:`~opentracing.Span` will not be automatically propagated to the child corotuines. :return: a :class:`~opentracing.Scope` instance to control the end of the active period for the :class:`~opentracing.Span`. It is a programming error to neglect to call :meth:`Scope.close()` on the returned instance. """ context = self._get_context() if context is None: return super(TornadoScopeManager, self).activate(span, finish_on_close) scope = _TornadoScope(self, span, finish_on_close) context.active = scope return scope @property def active(self): """ Return the currently active :class:`~opentracing.Scope` which can be used to access the currently active :attr:`Scope.span`. :return: the :class:`~opentracing.Scope` that is active, or ``None`` if not available. """ context = self._get_context() if not context: return super(TornadoScopeManager, self).active return context.active def _get_context(self): return _TracerRequestContextManager.current_context() class _TornadoScope(Scope): def __init__(self, manager, span, finish_on_close): super(_TornadoScope, self).__init__(manager, span) self._finish_on_close = finish_on_close self._to_restore = manager.active def close(self): context = self.manager._get_context() if context is None or context.active is not self: return context.active = self._to_restore if self._finish_on_close: self.span.finish() class ThreadSafeStackContext(tornado.stack_context.StackContext): """ Thread safe version of Tornado's StackContext (up to 4.3) Copy of implementation by caspersj@, until tornado-extras is open-sourced. Tornado's StackContext works as follows: - When entering a context, create an instance of StackContext and add add this instance to the current "context stack" - If execution transfers to another thread (using the wraps helper method), copy the current "context stack" and apply that in the new thread when execution starts - A context stack can be entered/exited by traversing the stack and calling enter/exit on all elements. This is how the `wraps` helper method enters/exits in new threads. - StackContext has an internal pointer to a context factory (i.e. RequestContext), and an internal stack of applied contexts (instances of RequestContext) for each instance of StackContext. RequestContext instances are entered/exited from the stack as the StackContext is entered/exited - However, the enter/exit logic and maintenance of this stack of RequestContext instances is not thread safe. ``` def __init__(self, context_factory): self.context_factory = context_factory self.contexts = [] self.active = True def enter(self): context = self.context_factory() self.contexts.append(context) context.__enter__() def exit(self, type, value, traceback): context = self.contexts.pop() context.__exit__(type, value, traceback) ``` Unexpected semantics of Tornado's default StackContext implementation: - There exist a race on `self.contexts`, where thread A enters a context, thread B enters a context, and thread A exits its context. In this case, the exit by thread A pops the instance created by thread B and calls exit on this instance. - There exists a race between `enter` and `exit` where thread A executes the two first statements of enter (create instance and add to contexts) and thread B executes exit, calling exit on an instance that has been initialized but not yet exited (and subsequently this instance will then be entered). The ThreadSafeStackContext changes the internal contexts stack to be thread local, fixing both of the above issues. """ def __init__(self, *args, **kwargs): class LocalContexts(threading.local): def __init__(self): super(LocalContexts, self).__init__() self._contexts = [] def append(self, item): self._contexts.append(item) def pop(self): return self._contexts.pop() super(ThreadSafeStackContext, self).__init__(*args, **kwargs) if hasattr(self, 'contexts'): # only patch if context exists self.contexts = LocalContexts() class _TracerRequestContext(object): __slots__ = ('active', ) def __init__(self, active=None): self.active = active class _TracerRequestContextManager(object): _state = threading.local() _state.context = None @classmethod def current_context(cls): return getattr(cls._state, 'context', None) def __init__(self, context): self._context = context def __enter__(self): self._prev_context = self.__class__.current_context() self.__class__._state.context = self._context return self._context def __exit__(self, *_): self.__class__._state.context = self._prev_context self._prev_context = None return False def tracer_stack_context(): """ Create a custom Tornado's :class:`StackContext` that allows :class:`TornadoScopeManager` to store the active :class:`~opentracing.Span` in the thread-local request context. Suppose you have a method ``handle_request(request)`` in the http server. Instead of calling it directly, use a wrapper: .. code-block:: python from opentracing.scope_managers.tornado import tracer_stack_context @tornado.gen.coroutine def handle_request_wrapper(request, actual_handler, *args, **kwargs) request_wrapper = TornadoRequestWrapper(request=request) span = http_server.before_request(request=request_wrapper) with tracer_stack_context(): with tracer.scope_manager.activate(span, True): return actual_handler(*args, **kwargs) :return: Return a custom :class:`StackContext` that allows :class:`TornadoScopeManager` to activate and propagate :class:`~opentracing.Span` instances. """ context = _TracerRequestContext() return ThreadSafeStackContext( lambda: _TracerRequestContextManager(context) ) opentracing-python-2.4.0/opentracing/span.py000066400000000000000000000201551375552321500212040ustar00rootroot00000000000000# Copyright The OpenTracing Authors # Copyright Uber Technologies, Inc # # 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. from __future__ import absolute_import from . import logs from opentracing.ext import tags class SpanContext(object): """SpanContext represents :class:`Span` state that must propagate to descendant :class:`Span`\\ s and across process boundaries. SpanContext is logically divided into two pieces: the user-level "Baggage" (see :meth:`Span.set_baggage_item` and :meth:`Span.get_baggage_item`) that propagates across :class:`Span` boundaries and any tracer-implementation-specific fields that are needed to identify or otherwise contextualize the associated :class:`Span` (e.g., a ``(trace_id, span_id, sampled)`` tuple). """ EMPTY_BAGGAGE = {} # TODO would be nice to make this immutable @property def baggage(self): """ Return baggage associated with this :class:`SpanContext`. If no baggage has been added to the :class:`Span`, returns an empty dict. The caller must not modify the returned dictionary. See also: :meth:`Span.set_baggage_item()` / :meth:`Span.get_baggage_item()` :rtype: dict :return: baggage associated with this :class:`SpanContext` or ``{}``. """ return SpanContext.EMPTY_BAGGAGE class Span(object): """ Span represents a unit of work executed on behalf of a trace. Examples of spans include a remote procedure call, or a in-process method call to a sub-component. Every span in a trace may have zero or more causal parents, and these relationships transitively form a DAG. It is common for spans to have at most one parent, and thus most traces are merely tree structures. Span implements a context manager API that allows the following usage:: with tracer.start_span(operation_name='go_fishing') as span: call_some_service() In the context manager syntax it's not necessary to call :meth:`Span.finish()` """ def __init__(self, tracer, context): self._tracer = tracer self._context = context @property def context(self): """Provides access to the :class:`SpanContext` associated with this :class:`Span`. The :class:`SpanContext` contains state that propagates from :class:`Span` to :class:`Span` in a larger trace. :rtype: SpanContext :return: the :class:`SpanContext` associated with this :class:`Span`. """ return self._context @property def tracer(self): """Provides access to the :class:`Tracer` that created this :class:`Span`. :rtype: Tracer :return: the :class:`Tracer` that created this :class:`Span`. """ return self._tracer def set_operation_name(self, operation_name): """Changes the operation name. :param operation_name: the new operation name :type operation_name: str :rtype: Span :return: the :class:`Span` itself, for call chaining. """ return self def finish(self, finish_time=None): """Indicates that the work represented by this :class:`Span` has completed or terminated. With the exception of the :attr:`Span.context` property, the semantics of all other :class:`Span` methods are undefined after :meth:`Span.finish()` has been invoked. :param finish_time: an explicit :class:`Span` finish timestamp as a unix timestamp per :meth:`time.time()` :type finish_time: float """ pass def set_tag(self, key, value): """Attaches a key/value pair to the :class:`Span`. The value must be a string, a bool, or a numeric type. If the user calls set_tag multiple times for the same key, the behavior of the :class:`Tracer` is undefined, i.e. it is implementation specific whether the :class:`Tracer` will retain the first value, or the last value, or pick one randomly, or even keep all of them. :param key: key or name of the tag. Must be a string. :type key: str :param value: value of the tag. :type value: string or bool or int or float :rtype: Span :return: the :class:`Span` itself, for call chaining. """ return self def log_kv(self, key_values, timestamp=None): """Adds a log record to the :class:`Span`. For example:: span.log_kv({ "event": "time to first byte", "packet.size": packet.size()}) span.log_kv({"event": "two minutes ago"}, time.time() - 120) :param key_values: A dict of string keys and values of any type :type key_values: dict :param timestamp: A unix timestamp per :meth:`time.time()`; current time if ``None`` :type timestamp: float :rtype: Span :return: the :class:`Span` itself, for call chaining. """ return self def set_baggage_item(self, key, value): """Stores a Baggage item in the :class:`Span` as a key/value pair. Enables powerful distributed context propagation functionality where arbitrary application data can be carried along the full path of request execution throughout the system. Note 1: Baggage is only propagated to the future (recursive) children of this :class:`Span`. Note 2: Baggage is sent in-band with every subsequent local and remote calls, so this feature must be used with care. :param key: Baggage item key :type key: str :param value: Baggage item value :type value: str :rtype: Span :return: itself, for chaining the calls. """ return self def get_baggage_item(self, key): """Retrieves value of the baggage item with the given key. :param key: key of the baggage item :type key: str :rtype: str :return: value of the baggage item with given key, or ``None``. """ return None def __enter__(self): """Invoked when :class:`Span` is used as a context manager. :rtype: Span :return: the :class:`Span` itself """ return self def __exit__(self, exc_type, exc_val, exc_tb): """Ends context manager and calls finish() on the :class:`Span`. If exception has occurred during execution, it is automatically logged and added as a tag. :attr:`~operation.ext.tags.ERROR` will also be set to `True`. """ Span._on_error(self, exc_type, exc_val, exc_tb) self.finish() @staticmethod def _on_error(span, exc_type, exc_val, exc_tb): if not span or not exc_val: return span.set_tag(tags.ERROR, True) span.log_kv({ logs.EVENT: tags.ERROR, logs.MESSAGE: str(exc_val), logs.ERROR_OBJECT: exc_val, logs.ERROR_KIND: exc_type, logs.STACK: exc_tb, }) def log_event(self, event, payload=None): """DEPRECATED""" if payload is None: return self.log_kv({logs.EVENT: event}) else: return self.log_kv({logs.EVENT: event, 'payload': payload}) def log(self, **kwargs): """DEPRECATED""" key_values = {} if logs.EVENT in kwargs: key_values[logs.EVENT] = kwargs[logs.EVENT] if 'payload' in kwargs: key_values['payload'] = kwargs['payload'] timestamp = None if 'timestamp' in kwargs: timestamp = kwargs['timestamp'] return self.log_kv(key_values, timestamp) opentracing-python-2.4.0/opentracing/tags.py000066400000000000000000000140671375552321500212060ustar00rootroot00000000000000# Copyright The OpenTracing Authors # Copyright Uber Technologies, Inc # # 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. from __future__ import absolute_import # The following tags are described in greater detail at the following url: # https://github.com/opentracing/specification/blob/master/semantic_conventions.md # Here we define standard names for tags that can be added to spans by the # instrumentation code. The actual tracing systems are not required to # retain these as tags in the stored spans if they have other means of # representing the same data. For example, the SPAN_KIND='server' can be # inferred from a Zipkin span by the presence of ss/sr annotations. # --------------------------------------------------------------------------- # SPAN_KIND hints at relationship between spans, e.g. client/server # --------------------------------------------------------------------------- SPAN_KIND = 'span.kind' # Marks a span representing the client-side of an RPC or other remote call SPAN_KIND_RPC_CLIENT = 'client' # Marks a span representing the server-side of an RPC or other remote call SPAN_KIND_RPC_SERVER = 'server' # Marks a span representing the consumer-side of a messaging call SPAN_KIND_CONSUMER = 'consumer' # Marks a span representing the producer-side of a messaging call SPAN_KIND_PRODUCER = 'producer' # --------------------------------------------------------------------------- # SERVICE indicates the service name for a span, which overrides any default # "service name" property defined in a tracer's config. The meaning of # service should correspond to the value set in peer.service, except it is # applied to the current span. This tag is meant to only be used when a # tracer is reporting spans on behalf of another service. This tag does not # need to be used when reporting spans for the service the tracer is running # in. # --------------------------------------------------------------------------- SERVICE = 'service' # --------------------------------------------------------------------------- # ERROR indicates whether a Span ended in an error state. # --------------------------------------------------------------------------- ERROR = 'error' # --------------------------------------------------------------------------- # COMPONENT (string) ia s low-cardinality identifier of the module, library, # or package that is generating a span. # --------------------------------------------------------------------------- COMPONENT = 'component' # --------------------------------------------------------------------------- # SAMPLING_PRIORITY (uint16) determines the priority of sampling this Span. # --------------------------------------------------------------------------- SAMPLING_PRIORITY = 'sampling.priority' # --------------------------------------------------------------------------- # PEER_* tags can be emitted by either client-side of server-side to describe # the other side/service in a peer-to-peer communications, like an RPC call. # --------------------------------------------------------------------------- # PEER_SERVICE (string) records the service name of the peer PEER_SERVICE = 'peer.service' # PEER_HOSTNAME (string) records the host name of the peer PEER_HOSTNAME = 'peer.hostname' # PEER_ADDRESS (string) suitable for use in a networking client library. # This may be a "ip:port", a bare "hostname", a FQDN, or even a # JDBC substring like "mysql://prod-db:3306" PEER_ADDRESS = 'peer.address' # PEER_HOST_IPV4 (uint32) records IP v4 host address of the peer PEER_HOST_IPV4 = 'peer.ipv4' # PEER_HOST_IPV6 (string) records IP v6 host address of the peer PEER_HOST_IPV6 = 'peer.ipv6' # PEER_PORT (uint16) records port number of the peer PEER_PORT = 'peer.port' # --------------------------------------------------------------------------- # HTTP tags # --------------------------------------------------------------------------- # HTTP_URL (string) should be the URL of the request being handled in this # segment of the trace, in standard URI format. The protocol is optional. HTTP_URL = 'http.url' # HTTP_METHOD (string) is the HTTP method of the request. # Both upper/lower case values are allowed. HTTP_METHOD = 'http.method' # HTTP_STATUS_CODE (int) is the numeric HTTP status code (200, 404, etc) # of the HTTP response. HTTP_STATUS_CODE = 'http.status_code' # --------------------------------------------------------------------------- # DATABASE tags # --------------------------------------------------------------------------- # DATABASE_INSTANCE (string) The database instance name. E.g., In java, if # the jdbc.url="jdbc:mysql://127.0.0.1:3306/customers", the instance # name is "customers" DATABASE_INSTANCE = 'db.instance' # DATABASE_STATEMENT (string) A database statement for the given database # type. E.g., for db.type="SQL", "SELECT * FROM user_table"; # for db.type="redis", "SET mykey 'WuValue'". DATABASE_STATEMENT = 'db.statement' # DATABASE_TYPE (string) For any SQL database, "sql". For others, # the lower-case database category, e.g. "cassandra", "hbase", or "redis". DATABASE_TYPE = 'db.type' # DATABASE_USER (string) Username for accessing database. E.g., # "readonly_user" or "reporting_user" DATABASE_USER = 'db.user' # --------------------------------------------------------------------------- # MESSAGE_BUS tags # --------------------------------------------------------------------------- # MESSAGE_BUS_DESTINATION (string) An address at which messages can be # exchanged. E.g. A Kafka record has an associated "topic name" that can # be extracted by the instrumented producer or consumer and stored # using this tag. MESSAGE_BUS_DESTINATION = 'message_bus.destination' opentracing-python-2.4.0/opentracing/tracer.py000066400000000000000000000313631375552321500215260ustar00rootroot00000000000000# Copyright The OpenTracing Authors # Copyright Uber Technologies, Inc # # 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. from __future__ import absolute_import from collections import namedtuple from .span import Span from .span import SpanContext from .scope import Scope from .scope_manager import ScopeManager from .propagation import Format, UnsupportedFormatException class Tracer(object): """Tracer is the entry point API between instrumentation code and the tracing implementation. This implementation both defines the public Tracer API, and provides a default no-op behavior. """ _supported_formats = [Format.TEXT_MAP, Format.BINARY, Format.HTTP_HEADERS] def __init__(self, scope_manager=None): self._scope_manager = ScopeManager() if scope_manager is None \ else scope_manager self._noop_span_context = SpanContext() self._noop_span = Span(tracer=self, context=self._noop_span_context) self._noop_scope = Scope(self._scope_manager, self._noop_span) @property def scope_manager(self): """Provides access to the current :class:`~opentracing.ScopeManager`. :rtype: :class:`~opentracing.ScopeManager` """ return self._scope_manager @property def active_span(self): """Provides access to the the active :class:`Span`. This is a shorthand for :attr:`Tracer.scope_manager.active.span`, and ``None`` will be returned if :attr:`Scope.span` is ``None``. :rtype: :class:`~opentracing.Span` :return: the active :class:`Span`. """ scope = self._scope_manager.active return None if scope is None else scope.span def start_active_span(self, operation_name, child_of=None, references=None, tags=None, start_time=None, ignore_active_span=False, finish_on_close=True): """Returns a newly started and activated :class:`Scope`. The returned :class:`Scope` supports with-statement contexts. For example:: with tracer.start_active_span('...') as scope: scope.span.set_tag('http.method', 'GET') do_some_work() # Span.finish() is called as part of scope deactivation through # the with statement. It's also possible to not finish the :class:`Span` when the :class:`Scope` context expires:: with tracer.start_active_span('...', finish_on_close=False) as scope: scope.span.set_tag('http.method', 'GET') do_some_work() # Span.finish() is not called as part of Scope deactivation as # `finish_on_close` is `False`. :param operation_name: name of the operation represented by the new :class:`Span` from the perspective of the current service. :type operation_name: str :param child_of: (optional) a :class:`Span` or :class:`SpanContext` instance representing the parent in a REFERENCE_CHILD_OF reference. If specified, the `references` parameter must be omitted. :type child_of: Span or SpanContext :param references: (optional) references that identify one or more parent :class:`SpanContext`\\ s. (See the Reference documentation for detail). :type references: :obj:`list` of :class:`Reference` :param tags: an optional dictionary of :class:`Span` tags. The caller gives up ownership of that dictionary, because the :class:`Tracer` may use it as-is to avoid extra data copying. :type tags: dict :param start_time: an explicit :class:`Span` start time as a unix timestamp per :meth:`time.time()`. :type start_time: float :param ignore_active_span: (optional) an explicit flag that ignores the current active :class:`Scope` and creates a root :class:`Span`. :type ignore_active_span: bool :param finish_on_close: whether :class:`Span` should automatically be finished when :meth:`Scope.close()` is called. :type finish_on_close: bool :rtype: Scope :return: a :class:`Scope`, already registered via the :class:`ScopeManager`. """ return self._noop_scope def start_span(self, operation_name=None, child_of=None, references=None, tags=None, start_time=None, ignore_active_span=False): """Starts and returns a new :class:`Span` representing a unit of work. Starting a root :class:`Span` (a :class:`Span` with no causal references):: tracer.start_span('...') Starting a child :class:`Span` (see also :meth:`start_child_span()`):: tracer.start_span( '...', child_of=parent_span) Starting a child :class:`Span` in a more verbose way:: tracer.start_span( '...', references=[opentracing.child_of(parent_span)]) :param operation_name: name of the operation represented by the new :class:`Span` from the perspective of the current service. :type operation_name: str :param child_of: (optional) a :class:`Span` or :class:`SpanContext` representing the parent in a REFERENCE_CHILD_OF reference. If specified, the `references` parameter must be omitted. :type child_of: Span or SpanContext :param references: (optional) references that identify one or more parent :class:`SpanContext`\\ s. (See the Reference documentation for detail). :type references: :obj:`list` of :class:`Reference` :param tags: an optional dictionary of :class:`Span` tags. The caller gives up ownership of that dictionary, because the :class:`Tracer` may use it as-is to avoid extra data copying. :type tags: dict :param start_time: an explicit Span start time as a unix timestamp per :meth:`time.time()` :type start_time: float :param ignore_active_span: an explicit flag that ignores the current active :class:`Scope` and creates a root :class:`Span`. :type ignore_active_span: bool :rtype: Span :return: an already-started :class:`Span` instance. """ return self._noop_span def inject(self, span_context, format, carrier): """Injects `span_context` into `carrier`. The type of `carrier` is determined by `format`. See the :class:`Format` class/namespace for the built-in OpenTracing formats. Implementations *must* raise :exc:`UnsupportedFormatException` if `format` is unknown or disallowed. :param span_context: the :class:`SpanContext` instance to inject :type span_context: SpanContext :param format: a python object instance that represents a given carrier format. `format` may be of any type, and `format` equality is defined by python ``==`` equality. :type format: Format :param carrier: the format-specific carrier object to inject into """ if format in Tracer._supported_formats: return raise UnsupportedFormatException(format) def extract(self, format, carrier): """Returns a :class:`SpanContext` instance extracted from a `carrier` of the given `format`, or ``None`` if no such :class:`SpanContext` could be found. The type of `carrier` is determined by `format`. See the :class:`Format` class/namespace for the built-in OpenTracing formats. Implementations *must* raise :exc:`UnsupportedFormatException` if `format` is unknown or disallowed. Implementations may raise :exc:`InvalidCarrierException`, :exc:`SpanContextCorruptedException`, or implementation-specific errors if there are problems with `carrier`. :param format: a python object instance that represents a given carrier format. `format` may be of any type, and `format` equality is defined by python ``==`` equality. :param carrier: the format-specific carrier object to extract from :rtype: SpanContext :return: a :class:`SpanContext` extracted from `carrier` or ``None`` if no such :class:`SpanContext` could be found. """ if format in Tracer._supported_formats: return self._noop_span_context raise UnsupportedFormatException(format) class ReferenceType(object): """A namespace for OpenTracing reference types. See http://opentracing.io/spec for more detail about references, reference types, and CHILD_OF and FOLLOWS_FROM in particular. """ CHILD_OF = 'child_of' FOLLOWS_FROM = 'follows_from' # We use namedtuple since references are meant to be immutable. # We subclass it to expose a standard docstring. class Reference(namedtuple('Reference', ['type', 'referenced_context'])): """A Reference pairs a reference type with a referenced :class:`SpanContext`. References are used by :meth:`Tracer.start_span()` to describe the relationships between :class:`Span`\\ s. :class:`Tracer` implementations must ignore references where referenced_context is ``None``. This behavior allows for simpler code when an inbound RPC request contains no tracing information and as a result :meth:`Tracer.extract()` returns ``None``:: parent_ref = tracer.extract(opentracing.HTTP_HEADERS, request.headers) span = tracer.start_span( 'operation', references=child_of(parent_ref) ) See :meth:`child_of` and :meth:`follows_from` helpers for creating these references. """ pass def child_of(referenced_context=None): """child_of is a helper that creates CHILD_OF References. :param referenced_context: the (causal parent) :class:`SpanContext` to reference. If ``None`` is passed, this reference must be ignored by the :class:`Tracer`. :type referenced_context: SpanContext :rtype: Reference :return: A reference suitable for ``Tracer.start_span(..., references=...)`` """ return Reference( type=ReferenceType.CHILD_OF, referenced_context=referenced_context) def follows_from(referenced_context=None): """follows_from is a helper that creates FOLLOWS_FROM References. :param referenced_context: the (causal parent) :class:`SpanContext` to reference. If ``None`` is passed, this reference must be ignored by the :class:`Tracer`. :type referenced_context: SpanContext :rtype: Reference :return: A Reference suitable for ``Tracer.start_span(..., references=...)`` """ return Reference( type=ReferenceType.FOLLOWS_FROM, referenced_context=referenced_context) def start_child_span(parent_span, operation_name, tags=None, start_time=None): """A shorthand method that starts a `child_of` :class:`Span` for a given parent :class:`Span`. Equivalent to calling:: parent_span.tracer().start_span( operation_name, references=opentracing.child_of(parent_span.context), tags=tags, start_time=start_time) :param parent_span: the :class:`Span` which will act as the parent in the returned :class:`Span`\\ s child_of reference. :type parent_span: Span :param operation_name: the operation name for the child :class:`Span` instance :type operation_name: str :param tags: optional dict of :class:`Span` tags. The caller gives up ownership of that dict, because the :class:`Tracer` may use it as-is to avoid extra data copying. :type tags: dict :param start_time: an explicit :class:`Span` start time as a unix timestamp per :meth:`time.time()`. :type start_time: float :rtype: Span :return: an already-started :class:`Span` instance. """ return parent_span.tracer.start_span( operation_name=operation_name, child_of=parent_span, tags=tags, start_time=start_time ) opentracing-python-2.4.0/requirements-test.txt000066400000000000000000000001011375552321500216060ustar00rootroot00000000000000# add dependencies in setup.py -r requirements.txt -e .[tests] opentracing-python-2.4.0/requirements.txt000066400000000000000000000000451375552321500206400ustar00rootroot00000000000000# add dependencies in setup.py -e . opentracing-python-2.4.0/setup.cfg000066400000000000000000000003251375552321500171760ustar00rootroot00000000000000[metadata] description-file = README.rst [flake8] max-line-length = 79 max-complexity = 8 exclude = ./docs/ [zest.releaser] release = no history_file = CHANGELOG.rst [upload_docs] upload-dir = docs/_build/html opentracing-python-2.4.0/setup.py000066400000000000000000000027251375552321500170750ustar00rootroot00000000000000from setuptools import setup setup( name='opentracing', version='2.4.0', author='The OpenTracing Authors', author_email='opentracing@googlegroups.com', description='OpenTracing API for Python. See documentation at http://opentracing.io', long_description='\n'+open('README.rst').read(), license='Apache License 2.0', url='https://github.com/opentracing/opentracing-python', keywords=['opentracing'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', ], packages=['opentracing'], include_package_data=True, zip_safe=False, platforms='any', install_requires=[ 'futures;python_version=="2.7"', ], extras_require={ 'tests': [ 'doubles', 'flake8', 'flake8-quotes', 'mock', 'pytest', 'pytest-cov', 'pytest-mock', 'Sphinx', 'sphinx_rtd_theme', 'six>=1.10.0,<2.0', 'gevent', 'tornado', ], }, ) opentracing-python-2.4.0/testbed/000077500000000000000000000000001375552321500170075ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/README.md000066400000000000000000000051601375552321500202700ustar00rootroot00000000000000# Testbed suite for the OpenTracing API Testbed suite designed to test API changes. ## Build and test. ```sh make testbed ``` Depending on whether Python 2 or 3 is being used, the `asyncio` tests will be automatically disabled. Alternatively, due to the organization of the suite, it's possible to run directly the tests using `py.test`: ```sh py.test -s testbed/test_multiple_callbacks/test_threads.py ``` ## Tested frameworks Currently the examples cover `threading`, `tornado`, `gevent`, `asyncio` (which requires Python 3) and `contextvars` (which requires Python 3.7 and higher). Each example uses their respective `ScopeManager` instance from `opentracing.scope_managers`, along with their related requirements and limitations. ### threading, asyncio and gevent No automatic `Span` propagation between parent and children tasks is provided, and thus the `Span` need to be manually passed down the chain. ### tornado `TornadoScopeManager` uses a variation of `tornado.stack_context.StackContext` to both store **and** automatically propagate the context from parent coroutines to their children. Currently, yielding over multiple children is not supported, as the context is effectively shared, and switching from coroutine to coroutine messes up the current active `Span`. ### contextvars `ContextVarsScopeManager` uses [contextvars](https://docs.python.org/3/library/contextvars.html) module to both store **and** automatically propagate the context from parent coroutines / tasks / scheduled in event loop callbacks to their children. ## 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 ina 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: ``` testbed/ test_new_pattern/ test_threads.py test_tornado.py test_asyncio.py test_gevent.py ``` Supporting all the platforms is optional, and a warning will be displayed when doing `make testbed` in such case. opentracing-python-2.4.0/testbed/__init__.py000066400000000000000000000000001375552321500211060ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/__main__.py000066400000000000000000000032301375552321500210770ustar00rootroot00000000000000from importlib import import_module import logging import os import sys import six import unittest from tornado import version_info as tornado_version enabled_platforms = [ 'threads', 'gevent', ] if tornado_version < (6, 0, 0, 0): # Including testbed for Tornado coroutines and stack context. # We don't need run testbed in case Tornado>=6, because it became # asyncio-based framework and `stack_context` was deprecated. enabled_platforms.append('tornado') if six.PY3: enabled_platforms.append('asyncio') if sys.version_info >= (3, 7): enabled_platforms.append('contextvars') logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__package__) def import_test_module(test_name, platform): full_path = '%s.%s.test_%s' % (__package__, test_name, platform) try: return import_module(full_path) except ImportError: pass return None def get_test_directories(): """Return all the directories starting with test_ under this package.""" return [directory for directory in os.listdir(os.path.dirname(__file__)) if directory.startswith('test_')] main_suite = unittest.TestSuite() loader = unittest.TestLoader() for test_dir in get_test_directories(): for platform in enabled_platforms: test_module = import_test_module(test_dir, platform) if test_module is None: logger.warning('Could not load %s for %s' % (test_dir, platform)) continue suite = loader.loadTestsFromModule(test_module) main_suite.addTests(suite) result = unittest.TextTestRunner(verbosity=3).run(main_suite) if result.failures or result.errors: sys.exit(1) opentracing-python-2.4.0/testbed/test_active_span_replacement/000077500000000000000000000000001375552321500247215ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_active_span_replacement/README.md000066400000000000000000000011011375552321500261710ustar00rootroot00000000000000# 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: ```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 ``` opentracing-python-2.4.0/testbed/test_active_span_replacement/__init__.py000066400000000000000000000000001375552321500270200ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_active_span_replacement/test_asyncio.py000066400000000000000000000034071375552321500300030ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..utils import stop_loop_when class TestAsyncio(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) 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) 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_id, 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)) opentracing-python-2.4.0/testbed/test_active_span_replacement/test_contextvars.py000066400000000000000000000034711375552321500307170ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase from opentracing.scope_managers.contextvars import ContextVarsScopeManager from ..utils import stop_loop_when class TestAsyncioContextVars(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) 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) self.loop.run_forever() initial, subtask, task = self.tracer.finished_spans() self.assertEmptySpan(initial, 'initial') self.assertEmptySpan(subtask, 'subtask') self.assertEmptySpan(task, 'task') # task/subtask are part of the same trace, # and subtask is a child of task self.assertSameTrace(subtask, task) self.assertIsChildOf(subtask, task) # initial task is not related in any way to those two tasks self.assertNotSameTrace(initial, subtask) self.assertHasNoParent(initial) 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)) opentracing-python-2.4.0/testbed/test_active_span_replacement/test_gevent.py000066400000000000000000000030731375552321500276250ustar00rootroot00000000000000from __future__ import print_function import gevent from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase class TestGevent(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(GeventScopeManager()) 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) gevent.wait(timeout=5.0) 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_id, 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): gevent.spawn(self.task, span) opentracing-python-2.4.0/testbed/test_active_span_replacement/test_threads.py000066400000000000000000000031211375552321500277610ustar00rootroot00000000000000from __future__ import print_function from concurrent.futures import ThreadPoolExecutor from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase class TestThreads(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer() 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_id, 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) opentracing-python-2.4.0/testbed/test_active_span_replacement/test_tornado.py000066400000000000000000000035501375552321500300030ustar00rootroot00000000000000from __future__ import print_function from tornado import gen, ioloop from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager, \ tracer_stack_context from ..testcase import OpenTracingTestCase from ..utils import stop_loop_when class TestTornado(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.loop = ioloop.IOLoop.current() 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') with tracer_stack_context(): self.submit_another_task(span) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) >= 3) self.loop.start() 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_id, None) @gen.coroutine 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.add_callback(self.task, span) opentracing-python-2.4.0/testbed/test_client_server/000077500000000000000000000000001375552321500227125ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_client_server/README.md000066400000000000000000000013541375552321500241740ustar00rootroot00000000000000# 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. ```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) ``` opentracing-python-2.4.0/testbed/test_client_server/__init__.py000066400000000000000000000000001375552321500250110ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_client_server/test_asyncio.py000066400000000000000000000050321375552321500257700ustar00rootroot00000000000000from __future__ import print_function import asyncio import opentracing from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_tag, stop_loop_when logger = get_logger(__name__) class Server(object): 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(object): 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(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) 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) 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)) opentracing-python-2.4.0/testbed/test_client_server/test_contextvars.py000066400000000000000000000050611375552321500267050ustar00rootroot00000000000000from __future__ import print_function import asyncio import opentracing from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.contextvars import ContextVarsScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_tag, stop_loop_when logger = get_logger(__name__) class Server(object): 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(object): 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 TestAsyncioContextVars(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) 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) 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)) opentracing-python-2.4.0/testbed/test_client_server/test_gevent.py000066400000000000000000000045331375552321500256200ustar00rootroot00000000000000from __future__ import print_function import gevent import gevent.queue import opentracing from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_tag logger = get_logger(__name__) class Server(object): 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 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(object): 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 TestGevent(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(GeventScopeManager()) self.queue = gevent.queue.Queue() self.server = Server(tracer=self.tracer, queue=self.queue) def test(self): client = Client(self.tracer, self.queue) gevent.spawn(self.server.run) gevent.spawn(client.send) gevent.wait(timeout=5.0) 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)) opentracing-python-2.4.0/testbed/test_client_server/test_threads.py000066400000000000000000000045131375552321500257600ustar00rootroot00000000000000from __future__ import print_function from threading import Thread from six.moves import queue import opentracing from opentracing.ext import tags from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase 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(object): 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(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer() self.queue = 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)) opentracing-python-2.4.0/testbed/test_client_server/test_tornado.py000066400000000000000000000052201375552321500257700ustar00rootroot00000000000000from __future__ import print_function from tornado import gen, ioloop, queues import opentracing from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager, \ tracer_stack_context from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_tag, stop_loop_when logger = get_logger(__name__) class Server(object): 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 @gen.coroutine def run(self): value = yield 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(object): def __init__(self, tracer, queue): self.tracer = tracer self.queue = queue @gen.coroutine 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) yield self.queue.put(message) logger.info('Sent message from client') class TestTornado(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.queue = queues.Queue() self.loop = ioloop.IOLoop.current() self.server = Server(tracer=self.tracer, queue=self.queue) def test(self): client = Client(self.tracer, self.queue) with tracer_stack_context(): self.loop.add_callback(self.server.run) self.loop.add_callback(client.send) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) >= 2) self.loop.start() 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)) opentracing-python-2.4.0/testbed/test_common_request_handler/000077500000000000000000000000001375552321500246035ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_common_request_handler/README.md000066400000000000000000000026741375552321500260730ustar00rootroot00000000000000# 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. - For `gevent` and `asyncio`, as no automatic `Span` propagation is done, an explicit `Span` has to be saved to be used as parent (observe an instrumentation library could help to do that implicitly - we stick to the simplest case, though). - For `tornado` and `contextvars`, as parent `Span` propagates automatically (even if the tasks are called through different coroutines), we **do** leverage the active `Span`. RequestHandler implementation: ```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. if self.ignore_active_span: # Used by threading, gevent and asyncio. span = self.tracer.start_span('send', child_of=self.context, ignore_active_span=True) else: # Used by tornado and contextvars. span = self.tracer.start_span('send') ``` opentracing-python-2.4.0/testbed/test_common_request_handler/__init__.py000066400000000000000000000000001375552321500267020ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_common_request_handler/request_handler.py000066400000000000000000000022321375552321500303410ustar00rootroot00000000000000from __future__ import print_function from opentracing.ext import tags from ..utils import get_logger logger = get_logger(__name__) class RequestHandler(object): 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): logger.info('After request %s' % request) span = request_context.get('span') if span is not None: span.finish() opentracing-python-2.4.0/testbed/test_common_request_handler/test_asyncio.py000066400000000000000000000105301375552321500276600ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_operation_name, stop_loop_when from .request_handler import RequestHandler logger = get_logger(__name__) class Client(object): 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(OpenTracingTestCase): """ 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(AsyncioScopeManager()) 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) self.loop.run_forever() self.assertEquals('message1::response', res_future1.result()) self.assertEquals('message2::response', res_future2.result()) spans = self.tracer.finished_spans() self.assertEquals(len(spans), 2) for span in spans: self.assertEquals(span.tags.get(tags.SPAN_KIND, None), tags.SPAN_KIND_RPC_CLIENT) self.assertNotSameTrace(spans[0], spans[1]) self.assertIsNone(spans[0].parent_id) self.assertIsNone(spans[1].parent_id) def test_parent_not_picked(self): """Active parent should not be picked up by child.""" async def do(): with self.tracer.start_active_span('parent'): response = await self.client.send_task('no_parent') self.assertEquals('no_parent::response', response) self.loop.run_until_complete(do()) spans = self.tracer.finished_spans() self.assertEquals(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 (we don't have better choice)""" async def do(): with self.tracer.start_active_span('parent') as scope: req_handler = RequestHandler(self.tracer, scope.span.context) client = Client(req_handler, self.loop) response = await client.send_task('correct_parent') self.assertEquals('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.assertEquals('wrong_parent::response', response) self.loop.run_until_complete(do()) spans = self.tracer.finished_spans() self.assertEquals(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) opentracing-python-2.4.0/testbed/test_common_request_handler/test_contextvars.py000066400000000000000000000104221375552321500305730ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.contextvars import ContextVarsScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_operation_name, stop_loop_when from .request_handler import RequestHandler logger = get_logger(__name__) class Client(object): 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 TestAsyncioContextVars(OpenTracingTestCase): """ There is only one instance of 'RequestHandler' per 'Client'. Methods of 'RequestHandler' are executed in different Tasks, but the context is the same, so we can leverage it for accessing the active span. """ def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) 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) 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.tags.get(tags.SPAN_KIND, None), tags.SPAN_KIND_RPC_CLIENT) self.assertNotSameTrace(spans[0], spans[1]) self.assertIsNone(spans[0].parent_id) self.assertIsNone(spans[1].parent_id) def test_parent_not_picked(self): """Active parent should not be picked up by child as we pass ignore_active_span=True to the RequestHandler""" async def do(): 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()) 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): """Solution is good because, though the RequestHandler being shared, the context will be properly detected.""" with self.tracer.start_active_span('parent'): req_handler = RequestHandler(self.tracer, ignore_active_span=False) client = Client(req_handler, self.loop) response = client.send_sync('correct_parent') self.assertEqual('correct_parent::response', response) # Should NOT be a child of the previously activated Span 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.assertIsNotChildOf(spans[2], parent_span) # Proper parent (none). opentracing-python-2.4.0/testbed/test_common_request_handler/test_gevent.py000066400000000000000000000076031375552321500275120ustar00rootroot00000000000000from __future__ import print_function import gevent from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_operation_name from .request_handler import RequestHandler logger = get_logger(__name__) class Client(object): def __init__(self, request_handler): self.request_handler = request_handler 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) gevent.spawn(before_handler).join() gevent.spawn(after_handler).join() return '%s::response' % message def send(self, message): return gevent.spawn(self.send_task, message) def send_sync(self, message, timeout=5.0): return gevent.spawn(self.send_task, message).get(timeout=timeout) class TestGevent(OpenTracingTestCase): """ There is only one instance of 'RequestHandler' per 'Client'. Methods of 'RequestHandler' are executed in different greenlets, 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(GeventScopeManager()) self.client = Client(RequestHandler(self.tracer)) def test_two_callbacks(self): response_greenlet1 = gevent.spawn(self.client.send_task, 'message1') response_greenlet2 = gevent.spawn(self.client.send_task, 'message2') gevent.joinall([response_greenlet1, response_greenlet2]) self.assertEquals('message1::response', response_greenlet1.get()) self.assertEquals('message2::response', response_greenlet2.get()) spans = self.tracer.finished_spans() self.assertEquals(len(spans), 2) for span in spans: self.assertEquals(span.tags.get(tags.SPAN_KIND, None), tags.SPAN_KIND_RPC_CLIENT) self.assertNotSameTrace(spans[0], spans[1]) self.assertIsNone(spans[0].parent_id) self.assertIsNone(spans[1].parent_id) 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.assertEquals('no_parent::response', response) spans = self.tracer.finished_spans() self.assertEquals(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 (we don't have better choice)""" with self.tracer.start_active_span('parent') as scope: client = Client(RequestHandler(self.tracer, scope.span.context)) response = client.send_sync('correct_parent') self.assertEquals('correct_parent::response', response) response = client.send_sync('wrong_parent') self.assertEquals('wrong_parent::response', response) spans = self.tracer.finished_spans() self.assertEquals(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) opentracing-python-2.4.0/testbed/test_common_request_handler/test_threads.py000066400000000000000000000076531375552321500276610ustar00rootroot00000000000000from __future__ import print_function from concurrent.futures import ThreadPoolExecutor from opentracing.ext import tags from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_operation_name from .request_handler import RequestHandler logger = get_logger(__name__) class Client(object): 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): f = self.executor.submit(self.send_task, message) return f.result(timeout=timeout) class TestThreads(OpenTracingTestCase): """ 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.assertEquals('message1::response', response_future1.result(5.0)) self.assertEquals('message2::response', response_future2.result(5.0)) spans = self.tracer.finished_spans() self.assertEquals(len(spans), 2) for span in spans: self.assertEquals(span.tags.get(tags.SPAN_KIND, None), tags.SPAN_KIND_RPC_CLIENT) self.assertNotSameTrace(spans[0], spans[1]) self.assertIsNone(spans[0].parent_id) self.assertIsNone(spans[1].parent_id) 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.assertEquals('no_parent::response', response) spans = self.tracer.finished_spans() self.assertEquals(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 (we don't have better choice)""" with self.tracer.start_active_span('parent') as scope: client = Client(RequestHandler(self.tracer, scope.span.context), self.executor) response = client.send_sync('correct_parent') self.assertEquals('correct_parent::response', response) response = client.send_sync('wrong_parent') self.assertEquals('wrong_parent::response', response) spans = self.tracer.finished_spans() self.assertEquals(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) opentracing-python-2.4.0/testbed/test_common_request_handler/test_tornado.py000066400000000000000000000106571375552321500276730ustar00rootroot00000000000000from __future__ import print_function import functools from tornado import gen, ioloop from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager, \ tracer_stack_context from ..testcase import OpenTracingTestCase from ..utils import get_logger, get_one_by_operation_name, stop_loop_when from .request_handler import RequestHandler logger = get_logger(__name__) class Client(object): def __init__(self, request_handler, loop): self.request_handler = request_handler self.loop = loop @gen.coroutine def send_task(self, message): request_context = {} @gen.coroutine def before_handler(): self.request_handler.before_request(message, request_context) @gen.coroutine def after_handler(): self.request_handler.after_request(message, request_context) yield before_handler() yield after_handler() raise gen.Return('%s::response' % message) def send(self, message): return self.send_task(message) def send_sync(self, message, timeout=5.0): return self.loop.run_sync(functools.partial(self.send_task, message), timeout) class TestTornado(OpenTracingTestCase): """ There is only one instance of 'RequestHandler' per 'Client'. Methods of 'RequestHandler' are executed in different coroutines but the StackContext is the same, so we can leverage it for accessing the active span. """ def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.loop = ioloop.IOLoop.current() self.client = Client(RequestHandler(self.tracer), self.loop) def test_two_callbacks(self): res_future1 = self.client.send('message1') res_future2 = self.client.send('message2') stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) >= 2) self.loop.start() self.assertEquals('message1::response', res_future1.result()) self.assertEquals('message2::response', res_future2.result()) spans = self.tracer.finished_spans() self.assertEquals(len(spans), 2) for span in spans: self.assertEquals(span.tags.get(tags.SPAN_KIND, None), tags.SPAN_KIND_RPC_CLIENT) self.assertNotSameTrace(spans[0], spans[1]) self.assertIsNone(spans[0].parent_id) self.assertIsNone(spans[1].parent_id) def test_parent_not_picked(self): """Active parent should not be picked up by child as we pass ignore_active_span=True to the RequestHandler""" with tracer_stack_context(): with self.tracer.start_active_span('parent'): response = self.client.send_sync('no_parent') self.assertEquals('no_parent::response', response) spans = self.tracer.finished_spans() self.assertEquals(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): """Solution is good because, though the RequestHandler being shared, the context will be properly detected.""" with tracer_stack_context(): with self.tracer.start_active_span('parent'): req_handler = RequestHandler(self.tracer, ignore_active_span=False) client = Client(req_handler, self.loop) response = client.send_sync('correct_parent') self.assertEquals('correct_parent::response', response) # Should NOT be a child of the previously activated Span response = client.send_sync('wrong_parent') self.assertEquals('wrong_parent::response', response) spans = self.tracer.finished_spans() self.assertEquals(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) # Proper parent (none). opentracing-python-2.4.0/testbed/test_late_span_finish/000077500000000000000000000000001375552321500233545ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_late_span_finish/README.md000066400000000000000000000012621375552321500246340ustar00rootroot00000000000000# 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). ```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) ``` opentracing-python-2.4.0/testbed/test_late_span_finish/__init__.py000066400000000000000000000000001375552321500254530ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_late_span_finish/test_asyncio.py000066400000000000000000000033011375552321500264270ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, stop_loop_when logger = get_logger(__name__) class TestAsyncio(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) 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) 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 i in range(2): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) self.assertTrue(spans[i].finish_time <= spans[-1].finish_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')) opentracing-python-2.4.0/testbed/test_late_span_finish/test_contextvars.py000066400000000000000000000026721375552321500273540ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.contextvars import ContextVarsScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, stop_loop_when logger = get_logger(__name__) class TestAsyncioContextVars(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) self.loop = asyncio.get_event_loop() def test_main(self): parent_scope = self.tracer.start_active_span('parent') self.submit_subtasks() stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) >= 2) self.loop.run_forever() # Late-finish the parent Span now. parent_scope.close() spans = self.tracer.finished_spans() self.assertEqual(len(spans), 3) self.assertNamesEqual(spans, ['task1', 'task2', 'parent']) for i in range(2): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) self.assertTrue(spans[i].finish_time <= spans[-1].finish_time) def submit_subtasks(self): async def task(name): logger.info('Running %s' % name) with self.tracer.start_active_span(name): await asyncio.sleep(0.1) self.loop.create_task(task('task1')) self.loop.create_task(task('task2')) opentracing-python-2.4.0/testbed/test_late_span_finish/test_gevent.py000066400000000000000000000027131375552321500262600ustar00rootroot00000000000000from __future__ import print_function import gevent from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger logger = get_logger(__name__) class TestGevent(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(GeventScopeManager()) 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) gevent.wait(timeout=5.0) # 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 i in range(2): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) self.assertTrue(spans[i].finish_time <= spans[-1].finish_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): with self.tracer.scope_manager.activate(parent_span, False): with self.tracer.start_active_span(name): gevent.sleep(0.1) gevent.spawn(task, 'task1') gevent.spawn(task, 'task2') opentracing-python-2.4.0/testbed/test_late_span_finish/test_threads.py000066400000000000000000000027731375552321500264300ustar00rootroot00000000000000from __future__ import print_function import time from concurrent.futures import ThreadPoolExecutor from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase class TestThreads(OpenTracingTestCase): 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 i in range(2): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) self.assertTrue(spans[i].finish_time <= spans[-1].finish_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) opentracing-python-2.4.0/testbed/test_late_span_finish/test_tornado.py000066400000000000000000000034411375552321500264350ustar00rootroot00000000000000from __future__ import print_function from tornado import gen, ioloop from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager, \ tracer_stack_context from ..testcase import OpenTracingTestCase from ..utils import get_logger, stop_loop_when logger = get_logger(__name__) class TestTornado(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.loop = ioloop.IOLoop.current() def test_main(self): # Create a Span and use it as (explicit) parent of a pair of subtasks. with tracer_stack_context(): parent_span = self.tracer.start_span('parent') self.submit_subtasks(parent_span) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) >= 2) self.loop.start() # 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 i in range(2): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) self.assertTrue(spans[i].finish_time <= spans[-1].finish_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): @gen.coroutine def task(name): logger.info('Running %s' % name) with self.tracer.scope_manager.activate(parent_span, False): with self.tracer.start_active_span(name): gen.sleep(0.1) self.loop.add_callback(task, 'task1') self.loop.add_callback(task, 'task2') opentracing-python-2.4.0/testbed/test_listener_per_request/000077500000000000000000000000001375552321500243115ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_listener_per_request/README.md000066400000000000000000000012131375552321500255650ustar00rootroot00000000000000# 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. ```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() ``` opentracing-python-2.4.0/testbed/test_listener_per_request/__init__.py000066400000000000000000000000001375552321500264100ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_listener_per_request/response_listener.py000066400000000000000000000002231375552321500304230ustar00rootroot00000000000000class ResponseListener(object): def __init__(self, span): self.span = span def on_response(self, res): self.span.finish() opentracing-python-2.4.0/testbed/test_listener_per_request/test_asyncio.py000066400000000000000000000025421375552321500273720ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_one_by_tag from .response_listener import ResponseListener class Client(object): 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(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) self.loop = asyncio.get_event_loop() def test_main(self): client = Client(self.tracer, self.loop) res = client.send_sync('message') self.assertEquals(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) opentracing-python-2.4.0/testbed/test_listener_per_request/test_contextvars.py000066400000000000000000000025701375552321500303060ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.contextvars import ContextVarsScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_one_by_tag from .response_listener import ResponseListener class Client(object): 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 TestAsyncioContextVars(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) 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) opentracing-python-2.4.0/testbed/test_listener_per_request/test_gevent.py000066400000000000000000000023661375552321500272210ustar00rootroot00000000000000from __future__ import print_function import gevent from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_one_by_tag from .response_listener import ResponseListener class Client(object): def __init__(self, tracer): self.tracer = tracer 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 gevent.spawn(self.task, message, listener).get() class TestGevent(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(GeventScopeManager()) def test_main(self): client = Client(self.tracer) res = client.send_sync('message') self.assertEquals(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) opentracing-python-2.4.0/testbed/test_listener_per_request/test_threads.py000066400000000000000000000024151375552321500273560ustar00rootroot00000000000000from __future__ import print_function from concurrent.futures import ThreadPoolExecutor from opentracing.ext import tags from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase from ..utils import get_one_by_tag from .response_listener import ResponseListener class Client(object): def __init__(self, tracer): self.tracer = tracer self.executor = ThreadPoolExecutor(max_workers=3) 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() class TestThreads(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer() def test_main(self): client = Client(self.tracer) res = client.send_sync('message') self.assertEquals(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) opentracing-python-2.4.0/testbed/test_listener_per_request/test_tornado.py000066400000000000000000000026701375552321500273750ustar00rootroot00000000000000from __future__ import print_function import functools from tornado import gen, ioloop from opentracing.ext import tags from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_one_by_tag from .response_listener import ResponseListener class Client(object): def __init__(self, tracer, loop): self.tracer = tracer self.loop = loop @gen.coroutine 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) task_func = functools.partial(self.task, message, listener) return self.loop.run_sync(task_func) class TestTornado(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.loop = ioloop.IOLoop.current() def test_main(self): client = Client(self.tracer, self.loop) res = client.send_sync('message') self.assertEquals(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) opentracing-python-2.4.0/testbed/test_multiple_callbacks/000077500000000000000000000000001375552321500237005ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_multiple_callbacks/README.md000066400000000000000000000037031375552321500251620ustar00rootroot00000000000000# 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 `gevent`, `tornado`, `asyncio` and `contextvars` the children coroutines representing the subtasks are simply yielded over, so no counter is needed. - For `tornado`, the invoked coroutines do not set any active `Span` as doing so messes the used `StackContext`. So yielding over **multiple** coroutines is not supported. - For `contextvars`, parent context is propagated to the children coroutines implicitly, manual context activation has been avoided. `threading` implementation: ```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: ```python 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) # Invoke and yield over the corotuines. with self.tracer.start_active_span('parent'): tasks = self.submit_callbacks() await asyncio.gather(*tasks) ``` opentracing-python-2.4.0/testbed/test_multiple_callbacks/__init__.py000066400000000000000000000000001375552321500257770ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_multiple_callbacks/test_asyncio.py000066400000000000000000000034721375552321500267640ustar00rootroot00000000000000from __future__ import print_function import random import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, stop_loop_when random.seed() logger = get_logger(__name__) class TestAsyncio(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) 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) self.loop.run_forever() spans = self.tracer.finished_spans() self.assertEquals(len(spans), 4) self.assertNamesEqual(spans, ['task', 'task', 'task', 'parent']) for i in range(3): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], 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 i in range(3): interval = 0.1 + random.randint(200, 500) * 0.001 t = self.loop.create_task(self.task(interval, parent_span)) tasks.append(t) return tasks opentracing-python-2.4.0/testbed/test_multiple_callbacks/test_contextvars.py000066400000000000000000000032541375552321500276750ustar00rootroot00000000000000from __future__ import print_function import random import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.contextvars import ContextVarsScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger, stop_loop_when random.seed() logger = get_logger(__name__) class TestAsyncioContextVars(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) 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) self.loop.run_forever() spans = self.tracer.finished_spans() self.assertEqual(len(spans), 4) self.assertNamesEqual(spans, ['task', 'task', 'task', 'parent']) for i in range(3): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) async def task(self, interval): logger.info('Starting task') with self.tracer.start_active_span('task'): await asyncio.sleep(interval) def submit_callbacks(self): tasks = [] for i in range(3): interval = 0.1 + random.randint(200, 500) * 0.001 t = self.loop.create_task(self.task(interval)) tasks.append(t) return tasks opentracing-python-2.4.0/testbed/test_multiple_callbacks/test_gevent.py000066400000000000000000000027761375552321500266150ustar00rootroot00000000000000from __future__ import print_function import random import gevent from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase from ..utils import get_logger random.seed() logger = get_logger(__name__) class TestGevent(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(GeventScopeManager()) def test_main(self): def main_task(): with self.tracer.start_active_span('parent'): tasks = self.submit_callbacks() gevent.joinall(tasks) gevent.spawn(main_task) gevent.wait(timeout=5.0) spans = self.tracer.finished_spans() self.assertEquals(len(spans), 4) self.assertNamesEqual(spans, ['task', 'task', 'task', 'parent']) for i in range(3): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) 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'): gevent.sleep(interval) def submit_callbacks(self): parent_span = self.tracer.scope_manager.active.span tasks = [] for i in range(3): interval = 0.1 + random.randint(200, 500) * 0.001 t = gevent.spawn(self.task, interval, parent_span) tasks.append(t) return tasks opentracing-python-2.4.0/testbed/test_multiple_callbacks/test_threads.py000066400000000000000000000034711375552321500267500ustar00rootroot00000000000000from __future__ import print_function import random import time from concurrent.futures import ThreadPoolExecutor from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase from ..utils import RefCount, get_logger random.seed() logger = get_logger(__name__) class TestThreads(OpenTracingTestCase): 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.assertEquals(len(spans), 4) self.assertNamesEqual(spans, ['task', 'task', 'task', 'parent']) for i in range(3): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], 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 i in range(3): parent_span._ref_count.incr() self.executor.submit(self.task, 0.1 + random.randint(200, 500) * .001, parent_span) opentracing-python-2.4.0/testbed/test_multiple_callbacks/test_tornado.py000066400000000000000000000037621375552321500267670ustar00rootroot00000000000000from __future__ import print_function import random from tornado import gen, ioloop from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager, \ tracer_stack_context from ..testcase import OpenTracingTestCase from ..utils import get_logger, stop_loop_when random.seed() logger = get_logger(__name__) class TestTornado(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.loop = ioloop.IOLoop.current() def test_main(self): @gen.coroutine def main_task(): with self.tracer.start_active_span('parent'): tasks = self.submit_callbacks() yield tasks with tracer_stack_context(): self.loop.add_callback(main_task) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 4) self.loop.start() spans = self.tracer.finished_spans() self.assertEquals(len(spans), 4) self.assertNamesEqual(spans, ['task', 'task', 'task', 'parent']) for i in range(3): self.assertSameTrace(spans[i], spans[-1]) self.assertIsChildOf(spans[i], spans[-1]) @gen.coroutine def task(self, interval, parent_span): logger.info('Starting task') # NOTE: No need to reactivate the parent_span, as TracerStackContext # keeps track of it, BUT a limitation is that, yielding # upon multiple coroutines, we cannot mess with the context, # so no active span set here. assert self.tracer.active_span is not None with self.tracer.start_span('task'): yield gen.sleep(interval) def submit_callbacks(self): parent_span = self.tracer.scope_manager.active.span tasks = [] for i in range(3): interval = 0.1 + random.randint(200, 500) * 0.001 t = self.task(interval, parent_span) tasks.append(t) return tasks opentracing-python-2.4.0/testbed/test_nested_callbacks/000077500000000000000000000000001375552321500233275ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_nested_callbacks/README.md000066400000000000000000000030161375552321500246060ustar00rootroot00000000000000# 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`, `gevent` and `asyncio` the `Span` is manually passed down the call chain, activating it in each corotuine/task. - For `tornado` and `contextvars`, the active `Span` is not passed down nor activated because the context is implicitly propagated. `threading` implementation: ```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') ... ``` `tornado` implementation: ```python @gen.coroutine def submit(self): span = self.tracer.scope_manager.active.span @gen.coroutine def task1(): self.assertEqual(span, self.tracer.scope_manager.active.span) span.set_tag('key1', '1') @gen.coroutine def task2(): self.assertEqual(span, self.tracer.scope_manager.active.span) span.set_tag('key2', '2') ``` opentracing-python-2.4.0/testbed/test_nested_callbacks/__init__.py000066400000000000000000000000001375552321500254260ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_nested_callbacks/test_asyncio.py000066400000000000000000000035571375552321500264170ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase from ..utils import stop_loop_when class TestAsyncio(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) 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) self.loop.run_forever() spans = self.tracer.finished_spans() self.assertEqual(len(spans), 1) self.assertEqual(spans[0].operation_name, 'one') for i in range(1, 4): self.assertEqual(spans[0].tags.get('key%s' % i, None), str(i)) def submit(self): span = self.tracer.scope_manager.active.span async def task1(): with self.tracer.scope_manager.activate(span, False): span.set_tag('key1', '1') async def task2(): with self.tracer.scope_manager.activate(span, False): span.set_tag('key2', '2') async def task3(): with self.tracer.scope_manager.activate(span, False): span.set_tag('key3', '3') span.finish() self.loop.create_task(task3()) self.loop.create_task(task2()) self.loop.create_task(task1()) opentracing-python-2.4.0/testbed/test_nested_callbacks/test_contextvars.py000066400000000000000000000256531375552321500273330ustar00rootroot00000000000000from __future__ import print_function import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.contextvars import ContextVarsScopeManager, \ no_parent_scope from ..testcase import OpenTracingTestCase from ..utils import stop_loop_when class TestAsyncioContextVars(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) self.loop = asyncio.get_event_loop() def test_main(self): def submit(): span = self.tracer.scope_manager.active.span async def task1(): self.assertEqual(span, self.tracer.active_span) self.tracer.active_span.set_tag('key1', '1') async def task2(): self.assertEqual(span, self.tracer.active_span) self.tracer.active_span.set_tag('key2', '2') async def task3(): self.assertEqual(span, self.tracer.active_span) self.tracer.active_span.set_tag('key3', '3') self.tracer.active_span.finish() self.loop.create_task(task3()) self.loop.create_task(task2()) self.loop.create_task(task1()) # 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): submit() self.loop.create_task(task()) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 1) self.loop.run_forever() span, = self.tracer.finished_spans() self.assertEqual(span.operation_name, 'one') for i in range(1, 4): self.assertEqual(span.tags.get('key%s' % i, None), str(i)) class TestAsyncioContextVarsScheduleInLoop(OpenTracingTestCase): # TODO: move the test-case to another file def setUp(self): self.tracer = MockTracer(ContextVarsScopeManager()) self.loop = asyncio.get_event_loop() def test_schedule_callbacks(self): def callback(op_name): with self.tracer.start_active_span( operation_name=op_name, child_of=self.tracer.active_span, ): pass def callback_with_nested_callback(op_name): with self.tracer.start_active_span( operation_name=op_name, child_of=self.tracer.active_span, ): self.loop.call_soon(callback, 'childof:{}'.format(op_name)) with self.tracer.start_active_span('root'): self.loop.call_soon(callback_with_nested_callback, 'first') self.loop.call_soon(callback, 'second') stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 4) self.loop.run_forever() root, first, second, childof_first = self.tracer.finished_spans() self.assertEmptySpan(root, 'root') self.assertEmptySpan(first, 'first') self.assertEmptySpan(second, 'second') self.assertEmptySpan(childof_first, 'childof:first') self.assertIsChildOf(first, root) self.assertIsChildOf(childof_first, first) self.assertIsChildOf(second, root) def test_coroutines_schedule_callbacks(self): def callback(op_name): with self.tracer.start_active_span( operation_name=op_name, child_of=self.tracer.active_span ): pass async def task(op_name): with self.tracer.start_active_span( operation_name=op_name, child_of=self.tracer.active_span ): self.loop.call_later( 0.1, callback, 'childof:{}'.format(op_name) ) with self.tracer.start_active_span('root'): self.loop.create_task(task('task1')) self.loop.create_task(task('task2')) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 5) self.loop.run_forever() root, task1, task2, child1, child2 = self.tracer.finished_spans() self.assertEmptySpan(root, 'root') self.assertEmptySpan(task1, 'task1') self.assertEmptySpan(task2, 'task2') self.assertEmptySpan(child1, 'childof:task1') self.assertEmptySpan(child2, 'childof:task2') self.assertIsChildOf(task1, root) self.assertIsChildOf(task2, root) self.assertIsChildOf(child1, task1) self.assertIsChildOf(child2, task2) def test_coroutines_scheduling_task(self): async def _task(op_name): await asyncio.sleep(0.1) with self.tracer.start_active_span( operation_name=op_name, child_of=self.tracer.active_span ): pass async def task(op_name): with self.tracer.start_active_span( operation_name=op_name, child_of=self.tracer.active_span ): self.loop.create_task(_task('childof:{}'.format(op_name))) with self.tracer.start_active_span('root'): self.loop.create_task(task('task1')) self.loop.create_task(task('task2')) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 5) self.loop.run_forever() root, task1, task2, child1, child2 = self.tracer.finished_spans() self.assertEmptySpan(root, 'root') self.assertEmptySpan(task1, 'task1') self.assertEmptySpan(task2, 'task2') self.assertEmptySpan(child1, 'childof:task1') self.assertEmptySpan(child2, 'childof:task2') self.assertIsChildOf(task1, root) self.assertIsChildOf(task2, root) self.assertIsChildOf(child1, task1) self.assertIsChildOf(child2, task2) def test_recursive_scheduling_task(self): tasks = 4 async def task(n=0): await asyncio.sleep(0.1) with self.tracer.start_active_span( operation_name=str(n), child_of=self.tracer.active_span ): if n < tasks: self.loop.create_task(task(n+1)) self.loop.create_task(task()) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == tasks) self.loop.run_forever() spans = self.tracer.finished_spans() for i in range(tasks): self.assertEmptySpan(spans[i], str(i)) if i == 0: self.assertIsNone(spans[i].parent_id) else: self.assertIsChildOf(spans[i], spans[i-1]) def test_recursive_scheduling_with_ignoring_active_span(self): tasks = 4 async def task(n=0): await asyncio.sleep(0.1) if n < tasks / 2: with self.tracer.start_active_span(str(n)): self.loop.create_task(task(n+1)) elif n < tasks: with self.tracer.start_active_span( operation_name=str(n), ignore_active_span=True ): self.loop.create_task(task(n+1)) self.loop.create_task(task()) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == tasks) self.loop.run_forever() s0, s1, s2, s3 = self.tracer.finished_spans() self.assertEmptySpan(s0, '0') self.assertHasNoParent(s0) self.assertEmptySpan(s1, '1') self.assertIsChildOf(s1, s0) self.assertEmptySpan(s2, '2') self.assertHasNoParent(s2) self.assertEmptySpan(s3, '3') self.assertHasNoParent(s3) def test_tasks_with_no_parent_scope(self): async def task(name): await asyncio.sleep(0.1) with self.tracer.start_active_span(name): await asyncio.sleep(0.1) async def tasks(): self.loop.create_task(task('task_1')) with no_parent_scope(): self.loop.create_task(task('task_2')) self.loop.create_task(task('task_3')) with self.tracer.start_active_span('root'): self.loop.create_task(tasks()) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 4) self.loop.run_forever() root, task1, task2, task3 = self.tracer.finished_spans() self.assertEmptySpan(root, 'root') self.assertEmptySpan(task1, 'task_1') self.assertIsChildOf(task1, root) # Third task was scheduled out `no_parent_scope`. self.assertEmptySpan(task3, 'task_3') self.assertIsChildOf(task3, root) # Second task "wrapped" by `no_parent_scope`. self.assertEmptySpan(task2, 'task_2') self.assertHasNoParent(task2) def test_callbacks_with_no_parent_scope(self): def callback(name): with self.tracer.start_active_span(name): pass def callbacks(): self.loop.call_soon(callback, 'task_1') with no_parent_scope(): self.loop.call_soon(callback, 'task_2') self.loop.call_soon(callback, 'task_3') with self.tracer.start_active_span('root'): self.loop.call_soon(callbacks) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 4) self.loop.run_forever() root, task1, task2, task3 = self.tracer.finished_spans() self.assertEmptySpan(root, 'root') self.assertEmptySpan(task1, 'task_1') self.assertIsChildOf(task1, root) # Third task was scheduled out `no_parent_scope`. self.assertEmptySpan(task3, 'task_3') self.assertIsChildOf(task3, root) # Second task "wrapped" by `no_parent_scope`. self.assertEmptySpan(task2, 'task_2') self.assertHasNoParent(task2) def test_await_with_no_parent_scope(self): async def coro(name): with self.tracer.start_active_span(name): pass async def main_coro(): await coro('coro_1') with no_parent_scope(): await coro('coro_2') await coro('coro_3') with self.tracer.start_active_span('root'): self.loop.create_task(main_coro()) stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 4) self.loop.run_forever() root, coro1, coro2, coro3 = self.tracer.finished_spans() self.assertEmptySpan(root, 'root') self.assertEmptySpan(coro1, 'coro_1') self.assertIsChildOf(coro1, root) # second coroutine "wrapped" by `no_parent_scope`. self.assertEmptySpan(coro2, 'coro_2') self.assertHasNoParent(coro2) self.assertEmptySpan(coro3, 'coro_3') self.assertIsChildOf(coro3, root) opentracing-python-2.4.0/testbed/test_nested_callbacks/test_gevent.py000066400000000000000000000027741375552321500262420ustar00rootroot00000000000000from __future__ import print_function import gevent from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase class TestGevent(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(GeventScopeManager()) 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() gevent.wait() spans = self.tracer.finished_spans() self.assertEqual(len(spans), 1) self.assertEqual(spans[0].operation_name, 'one') for i in range(1, 4): self.assertEqual(spans[0].tags.get('key%s' % i, None), str(i)) 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') gevent.spawn(task3) gevent.spawn(task2) gevent.spawn(task1) opentracing-python-2.4.0/testbed/test_nested_callbacks/test_threads.py000066400000000000000000000035371375552321500264020ustar00rootroot00000000000000from __future__ import print_function from concurrent.futures import ThreadPoolExecutor from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase from ..utils import await_until class TestThreads(OpenTracingTestCase): 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].operation_name, 'one') for i in range(1, 4): self.assertEqual(spans[0].tags.get('key%s' % i, None), str(i)) 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) opentracing-python-2.4.0/testbed/test_nested_callbacks/test_tornado.py000066400000000000000000000037711375552321500264160ustar00rootroot00000000000000from __future__ import print_function from tornado import gen, ioloop from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager, \ tracer_stack_context from ..testcase import OpenTracingTestCase from ..utils import stop_loop_when class TestTornado(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.loop = ioloop.IOLoop.current() def test_main(self): # Start a Span and let the callback-chain # finish it when the task is done with tracer_stack_context(): with self.tracer.start_active_span('one', finish_on_close=False): self.submit() stop_loop_when(self.loop, lambda: len(self.tracer.finished_spans()) == 1) self.loop.start() spans = self.tracer.finished_spans() self.assertEqual(len(spans), 1) self.assertEqual(spans[0].operation_name, 'one') for i in range(1, 4): self.assertEqual(spans[0].tags.get('key%s' % i, None), str(i)) # Since StackContext propagates the active Span # from the first callback, we don't need to re-activate # it later on anymore. @gen.coroutine def submit(self): span = self.tracer.scope_manager.active.span @gen.coroutine def task1(): self.assertEqual(span, self.tracer.scope_manager.active.span) span.set_tag('key1', '1') @gen.coroutine def task2(): self.assertEqual(span, self.tracer.scope_manager.active.span) span.set_tag('key2', '2') @gen.coroutine def task3(): self.assertEqual(span, self.tracer.scope_manager.active.span) span.set_tag('key3', '3') span.finish() yield task3() yield task2() yield task1() opentracing-python-2.4.0/testbed/test_subtask_span_propagation/000077500000000000000000000000001375552321500251465ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_subtask_span_propagation/README.md000066400000000000000000000031341375552321500264260ustar00rootroot00000000000000# 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`, `gevent` and `asyncio` the `Span` is manually passed down the call chain, being manually reactivated it in each corotuine/task. - For `tornado` and `contextvars`, the active `Span` is not passed down the chain nor activated because the context is implicitly propagated. `threading` implementation: ```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 ``` `tornado` implementation: ```python def parent_task(self, message): with self.tracer.start_active_span('parent'): res = yield self.child_task(message) raise gen.Return(res) @gen.coroutine 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'): raise gen.Return('%s::response' % message) ``` opentracing-python-2.4.0/testbed/test_subtask_span_propagation/__init__.py000066400000000000000000000000001375552321500272450ustar00rootroot00000000000000opentracing-python-2.4.0/testbed/test_subtask_span_propagation/test_asyncio.py000066400000000000000000000021701375552321500302240ustar00rootroot00000000000000from __future__ import absolute_import, print_function import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase class TestAsyncio(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) 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') as scope: res = await self.child_task(message, scope.span) return res async 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 opentracing-python-2.4.0/testbed/test_subtask_span_propagation/test_contextvars.py000066400000000000000000000020271375552321500311400ustar00rootroot00000000000000from __future__ import absolute_import, print_function import asyncio from opentracing.mocktracer import MockTracer from opentracing.scope_managers.asyncio import AsyncioScopeManager from ..testcase import OpenTracingTestCase class TestAsyncioContextVars(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(AsyncioScopeManager()) 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') child, parent = self.tracer.finished_spans() self.assertEmptySpan(child, 'child') self.assertEmptySpan(parent, 'parent') self.assertIsChildOf(child, parent) async def parent_task(self, message): with self.tracer.start_active_span('parent'): res = await self.child_task(message) return res async def child_task(self, message): with self.tracer.start_active_span('child'): return '%s::response' % message opentracing-python-2.4.0/testbed/test_subtask_span_propagation/test_gevent.py000066400000000000000000000020661375552321500300530ustar00rootroot00000000000000from __future__ import absolute_import, print_function import gevent from opentracing.mocktracer import MockTracer from opentracing.scope_managers.gevent import GeventScopeManager from ..testcase import OpenTracingTestCase class TestGevent(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(GeventScopeManager()) def test_main(self): res = gevent.spawn(self.parent_task, 'message').get() 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: res = gevent.spawn(self.child_task, message, scope.span).get() 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 opentracing-python-2.4.0/testbed/test_subtask_span_propagation/test_threads.py000066400000000000000000000021501375552321500302070ustar00rootroot00000000000000from __future__ import absolute_import, print_function from concurrent.futures import ThreadPoolExecutor from opentracing.mocktracer import MockTracer from ..testcase import OpenTracingTestCase class TestThreads(OpenTracingTestCase): 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: 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 opentracing-python-2.4.0/testbed/test_subtask_span_propagation/test_tornado.py000066400000000000000000000024571375552321500302350ustar00rootroot00000000000000from __future__ import absolute_import, print_function import functools from tornado import gen, ioloop from opentracing.mocktracer import MockTracer from opentracing.scope_managers.tornado import TornadoScopeManager, \ tracer_stack_context from ..testcase import OpenTracingTestCase class TestTornado(OpenTracingTestCase): def setUp(self): self.tracer = MockTracer(TornadoScopeManager()) self.loop = ioloop.IOLoop.current() def test_main(self): parent_task = functools.partial(self.parent_task, 'message') with tracer_stack_context(): res = self.loop.run_sync(parent_task) 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]) @gen.coroutine def parent_task(self, message): with self.tracer.start_active_span('parent'): res = yield self.child_task(message) raise gen.Return(res) @gen.coroutine 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'): raise gen.Return('%s::response' % message) opentracing-python-2.4.0/testbed/testcase.py000066400000000000000000000020001375552321500211640ustar00rootroot00000000000000import unittest class OpenTracingTestCase(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): return self.assertEqual(spanA.parent_id, spanB.context.span_id) def assertIsNotChildOf(self, spanA, spanB): return self.assertNotEqual(spanA.parent_id, spanB.context.span_id) def assertHasNoParent(self, span): return self.assertIsNone(span.parent_id) def assertNamesEqual(self, spans, names): self.assertEqual(list(map(lambda x: x.operation_name, spans)), names) def assertEmptySpan(self, span, name): self.assertEqual(span.operation_name, name) self.assertEqual(span.tags, {}) self.assertEqual(len(span.logs), 0) opentracing-python-2.4.0/testbed/utils.py000066400000000000000000000041611375552321500205230ustar00rootroot00000000000000from __future__ import print_function import logging import six import threading import time class RefCount(object): """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.tags.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.operation_name == name: found.append(span) if len(found) > 1: raise RuntimeError('Too many values') return found[0] if len(found) > 0 else None def get_tags_count(span, prefix): """Returns the tag count with the given prefix from a Span""" test_keys = set() for key in six.iterkeys(span.tags): if key.startswith(prefix): test_keys.add(key) return len(test_keys) opentracing-python-2.4.0/tests/000077500000000000000000000000001375552321500165175ustar00rootroot00000000000000opentracing-python-2.4.0/tests/__init__.py000066400000000000000000000000001375552321500206160ustar00rootroot00000000000000opentracing-python-2.4.0/tests/conftest.py000066400000000000000000000026211375552321500207170ustar00rootroot00000000000000# Copyright (c) 2016 The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import sys import six PYTHON3_FILES = [ 'scope_managers/test_asyncio.py', ] PYTHON37_FILES = [ 'scope_managers/test_contextvars.py', ] collect_ignore = [] if six.PY2: collect_ignore += PYTHON3_FILES if sys.version_info < (3, 7): collect_ignore += PYTHON37_FILES opentracing-python-2.4.0/tests/ext/000077500000000000000000000000001375552321500173175ustar00rootroot00000000000000opentracing-python-2.4.0/tests/ext/__init__.py000066400000000000000000000000001375552321500214160ustar00rootroot00000000000000opentracing-python-2.4.0/tests/mocktracer/000077500000000000000000000000001375552321500206515ustar00rootroot00000000000000opentracing-python-2.4.0/tests/mocktracer/__init__.py000066400000000000000000000000001375552321500227500ustar00rootroot00000000000000opentracing-python-2.4.0/tests/mocktracer/test_api.py000066400000000000000000000031041375552321500230310ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import unittest from opentracing.harness.api_check import APICompatibilityCheckMixin from opentracing.mocktracer import MockTracer class APICheckMockTracer(unittest.TestCase, APICompatibilityCheckMixin): def tracer(self): return MockTracer() def is_parent(self, parent, span): # use `Span` ids to check parenting if parent is None: return span.parent_id is None return parent.context.span_id == span.parent_id opentracing-python-2.4.0/tests/mocktracer/test_propagation.py000066400000000000000000000052661375552321500246160ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import pytest from opentracing import Format, SpanContextCorruptedException, \ UnsupportedFormatException from opentracing.mocktracer import MockTracer def test_propagation(): tracer = MockTracer() sp = tracer.start_span(operation_name='test') sp.set_baggage_item('foo', 'bar') # Test invalid types with pytest.raises(UnsupportedFormatException): tracer.inject(sp.context, 'invalid', {}) with pytest.raises(UnsupportedFormatException): tracer.extract('invalid', {}) tests = [(Format.BINARY, bytearray()), (Format.TEXT_MAP, {})] for format, carrier in tests: tracer.inject(sp.context, format, carrier) extracted_ctx = tracer.extract(format, carrier) assert extracted_ctx.trace_id == sp.context.trace_id assert extracted_ctx.span_id == sp.context.span_id assert extracted_ctx.baggage == sp.context.baggage def test_propagation_extract_corrupted_data(): tracer = MockTracer() tests = [(Format.BINARY, bytearray()), (Format.TEXT_MAP, {})] for format, carrier in tests: with pytest.raises(SpanContextCorruptedException): tracer.extract(format, carrier) def test_start_span(): """ Test in process child span creation.""" tracer = MockTracer() sp = tracer.start_span(operation_name='test') sp.set_baggage_item('foo', 'bar') child = tracer.start_span( operation_name='child', child_of=sp.context) assert child.context.trace_id == sp.context.trace_id assert child.context.baggage == sp.context.baggage assert child.parent_id == sp.context.span_id opentracing-python-2.4.0/tests/mocktracer/test_span.py000066400000000000000000000031351375552321500232250ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from opentracing.mocktracer import MockTracer def test_span_log_kv(): tracer = MockTracer() span = tracer.start_span('x') span.log_kv({ 'foo': 'bar', 'baz': 42, }) span.finish() finished_spans = tracer.finished_spans() assert len(finished_spans) == 1 assert len(finished_spans[0].logs) == 1 assert len(finished_spans[0].logs[0].key_values) == 2 assert finished_spans[0].logs[0].key_values['foo'] == 'bar' assert finished_spans[0].logs[0].key_values['baz'] == 42 opentracing-python-2.4.0/tests/mocktracer/test_tracer.py000066400000000000000000000032711375552321500235450ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from opentracing.mocktracer import MockTracer def test_tracer_finished_spans(): tracer = MockTracer() span_x = tracer.start_span('x') span_x.finish() span_y = tracer.start_span('y') span_y.finish() finished_spans = tracer.finished_spans() assert len(finished_spans) == 2 assert finished_spans[0] == span_x assert finished_spans[1] == span_y # A copy per invocation. assert tracer.finished_spans() is not finished_spans def test_tracer_reset(): tracer = MockTracer() tracer.start_span('x').finish() tracer.reset() assert len(tracer.finished_spans()) == 0 opentracing-python-2.4.0/tests/scope_managers/000077500000000000000000000000001375552321500215055ustar00rootroot00000000000000opentracing-python-2.4.0/tests/scope_managers/__init__.py000066400000000000000000000000001375552321500236040ustar00rootroot00000000000000opentracing-python-2.4.0/tests/scope_managers/test_asyncio.py000066400000000000000000000037661375552321500245770ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from concurrent.futures import ThreadPoolExecutor from unittest import TestCase import asyncio from opentracing.scope_managers.asyncio import AsyncioScopeManager from opentracing.harness.scope_check import ScopeCompatibilityCheckMixin class AsyncioCompabilityCheck(TestCase, ScopeCompatibilityCheckMixin): def scope_manager(self): return AsyncioScopeManager() def run_test(self, test_fn): @asyncio.coroutine def async_test_fn(): test_fn() asyncio.get_event_loop().run_until_complete(async_test_fn()) def test_no_event_loop(self): # no event loop exists by default in # new threads, so make sure we don't fail there. def test_fn(): manager = self.scope_manager() assert manager.active is None executor = ThreadPoolExecutor(max_workers=1) executor.submit(test_fn).result() opentracing-python-2.4.0/tests/scope_managers/test_contextvars.py000066400000000000000000000040221375552321500254740ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from concurrent.futures import ThreadPoolExecutor from unittest import TestCase import asyncio from opentracing.scope_managers.contextvars import ContextVarsScopeManager from opentracing.harness.scope_check import ScopeCompatibilityCheckMixin class AsyncioContextVarsCompabilityCheck( TestCase, ScopeCompatibilityCheckMixin ): def scope_manager(self): return ContextVarsScopeManager() def run_test(self, test_fn): @asyncio.coroutine def async_test_fn(): test_fn() asyncio.get_event_loop().run_until_complete(async_test_fn()) def test_no_event_loop(self): # no event loop exists by default in # new threads, so make sure we don't fail there. def test_fn(): manager = self.scope_manager() assert manager.active is None executor = ThreadPoolExecutor(max_workers=1) executor.submit(test_fn).result() opentracing-python-2.4.0/tests/scope_managers/test_gevent.py000066400000000000000000000027721375552321500244160ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from unittest import TestCase import gevent from opentracing.scope_managers.gevent import GeventScopeManager from opentracing.harness.scope_check import ScopeCompatibilityCheckMixin class GeventCompabilityCheck(TestCase, ScopeCompatibilityCheckMixin): def scope_manager(self): return GeventScopeManager() def run_test(self, test_fn): gevent.spawn(test_fn).get() opentracing-python-2.4.0/tests/scope_managers/test_threadlocal.py000066400000000000000000000026541375552321500254070ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from unittest import TestCase from opentracing.scope_managers import ThreadLocalScopeManager from opentracing.harness.scope_check import ScopeCompatibilityCheckMixin class ThreadLocalCompabilityCheck(TestCase, ScopeCompatibilityCheckMixin): def scope_manager(self): return ThreadLocalScopeManager() opentracing-python-2.4.0/tests/scope_managers/test_tornado.py000066400000000000000000000036521375552321500245720ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import pytest from unittest import TestCase from tornado import ioloop, version_info try: from opentracing.scope_managers.tornado import TornadoScopeManager from opentracing.scope_managers.tornado import tracer_stack_context except ImportError: pass from opentracing.harness.scope_check import ScopeCompatibilityCheckMixin # We don't need run tests in case Tornado>=6, because it became # asyncio-based framework and `stack_context` was deprecated. @pytest.mark.skipif(version_info >= (6, 0, 0, 0), reason='skip Tornado >= 6') class TornadoCompabilityCheck(TestCase, ScopeCompatibilityCheckMixin): def scope_manager(self): return TornadoScopeManager() def run_test(self, test_fn): with tracer_stack_context(): ioloop.IOLoop.current().run_sync(test_fn) opentracing-python-2.4.0/tests/test_api.py000066400000000000000000000020471375552321500207040ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import import unittest from opentracing import Tracer from opentracing.harness.api_check import APICompatibilityCheckMixin class APICheckNoopTracer(unittest.TestCase, APICompatibilityCheckMixin): """ Run tests from APICompatibilityCheckMixin against default No-op Tracer. """ def tracer(self): return Tracer() def check_baggage_values(self): return False def check_scope_manager(self): return False opentracing-python-2.4.0/tests/test_api_check_mixin.py000066400000000000000000000055641375552321500232540ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import import unittest from opentracing import Tracer from opentracing.harness.api_check import APICompatibilityCheckMixin class VerifyAPICompatibilityCheck(unittest.TestCase): def test_tracer_exception(self): api_check = APICompatibilityCheckMixin() with self.assertRaises(NotImplementedError): api_check.tracer() def test_default_baggage_check_mode(self): api_check = APICompatibilityCheckMixin() assert api_check.check_baggage_values() is True def test_default_scope_manager_check_mode(self): api_check = APICompatibilityCheckMixin() assert api_check.check_scope_manager() is True def test_baggage_check_works(self): api_check = APICompatibilityCheckMixin() setattr(api_check, 'tracer', lambda: Tracer()) # no-op tracer does not store baggage, so the test with default # value of `check_baggage_values()` should fail. with self.assertRaises(AssertionError): api_check.test_span_baggage() # second check that assert on empty baggage will fail too with self.assertRaises(AssertionError): api_check.test_context_baggage() def test_scope_manager_check_works(self): api_check = APICompatibilityCheckMixin() setattr(api_check, 'tracer', lambda: Tracer()) # these tests are expected to succeed api_check.test_start_active_span_ignore_active_span() api_check.test_start_span_propagation_ignore_active_span() # no-op tracer has a no-op ScopeManager implementation, # which means no *actual* propagation is done, # so these tests are expected to work, but asserts to fail with self.assertRaises(AssertionError): api_check.test_start_active_span() with self.assertRaises(AssertionError): api_check.test_start_active_span_parent() with self.assertRaises(AssertionError): api_check.test_start_span_propagation() with self.assertRaises(AssertionError): api_check.test_tracer_start_active_span_scope() with self.assertRaises(AssertionError): api_check.test_tracer_start_span_scope() with self.assertRaises(AssertionError): api_check.test_start_active_span_finish_on_close() opentracing-python-2.4.0/tests/test_globaltracer.py000066400000000000000000000027531375552321500226000ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import import pytest import mock import opentracing def teardown_function(function): opentracing._reset_global_tracer() def test_opentracing_tracer(): assert opentracing.tracer is opentracing.global_tracer() assert isinstance(opentracing.global_tracer(), opentracing.Tracer) def test_is_global_tracer_registered(): assert opentracing.is_global_tracer_registered() is False def test_set_global_tracer(): tracer = mock.Mock() opentracing.set_global_tracer(tracer) assert opentracing.global_tracer() is tracer assert opentracing.is_global_tracer_registered() # Register another value. tracer = mock.Mock() opentracing.set_global_tracer(tracer) assert opentracing.global_tracer() is tracer assert opentracing.is_global_tracer_registered() def test_register_none(): with pytest.raises(ValueError): opentracing.set_global_tracer(None) opentracing-python-2.4.0/tests/test_noop_span.py000066400000000000000000000101271375552321500221250ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import import mock import time import types from opentracing import child_of from opentracing import Format from opentracing import Tracer from opentracing import logs from opentracing import tags def test_span(): tracer = Tracer() parent = tracer.start_span('parent') child = tracer.start_span('test', references=child_of(parent.context)) assert parent == child child.log_kv({'event': 'cache_hit', 'size.bytes': 42}) child.log_kv({'event': 'cache_miss'}, time.time()) child.log_event('cache_hit', ['arg1', 'arg2']) with mock.patch.object(parent, 'finish') as finish: with mock.patch.object(parent, 'log_event') as log_event: with mock.patch.object(parent, 'log_kv') as log_kv: with mock.patch.object(parent, 'set_tag') as set_tag: try: with parent: raise ValueError() except ValueError: pass assert finish.call_count == 1 assert log_event.call_count == 0 assert log_kv.call_count == 1 assert set_tag.call_count == 1 with mock.patch.object(parent, 'finish') as finish: with mock.patch.object(parent, 'log_event') as log_kv: with parent: pass assert finish.call_count == 1 assert log_kv.call_count == 0 parent.set_tag('x', 'y').set_tag('z', 1) # test chaining parent.set_tag(tags.PEER_SERVICE, 'test-service') parent.set_tag(tags.PEER_HOST_IPV4, 127 << 24 + 1) parent.set_tag(tags.PEER_HOST_IPV6, '::') parent.set_tag(tags.PEER_HOSTNAME, 'uber.com') parent.set_tag(tags.PEER_PORT, 123) parent.finish() def test_span_error_report(): tracer = Tracer() span = tracer.start_span('foo') error_message = 'unexpected_situation' with mock.patch.object(span, 'log_kv') as log_kv: with mock.patch.object(span, 'set_tag') as set_tag: try: with span: raise ValueError(error_message) except ValueError: pass assert set_tag.call_count == 1 assert set_tag.call_args[0] == (tags.ERROR, True) assert log_kv.call_count == 1 log_kv_args = log_kv.call_args[0][0] assert log_kv_args.get(logs.EVENT, None) is tags.ERROR assert log_kv_args.get(logs.MESSAGE, None) is error_message assert log_kv_args.get(logs.ERROR_KIND, None) is ValueError assert isinstance(log_kv_args.get(logs.ERROR_OBJECT, None), ValueError) assert isinstance(log_kv_args.get(logs.STACK, None), types.TracebackType) def test_inject(): tracer = Tracer() span = tracer.start_span() bin_carrier = bytearray() tracer.inject( span_context=span.context, format=Format.BINARY, carrier=bin_carrier) assert bin_carrier == bytearray() text_carrier = {} tracer.inject( span_context=span.context, format=Format.TEXT_MAP, carrier=text_carrier) assert text_carrier == {} def test_extract(): tracer = Tracer() noop_span = tracer._noop_span bin_carrier = bytearray() span_ctx = tracer.extract(Format.BINARY, carrier=bin_carrier) assert noop_span.context == span_ctx text_carrier = {} span_ctx = tracer.extract(Format.TEXT_MAP, carrier=text_carrier) assert noop_span.context == span_ctx opentracing-python-2.4.0/tests/test_noop_tracer.py000066400000000000000000000020121375552321500224360ustar00rootroot00000000000000# Copyright The OpenTracing 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. from __future__ import absolute_import from opentracing import child_of from opentracing import Tracer def test_tracer(): tracer = Tracer() span = tracer.start_span(operation_name='root') child = tracer.start_span(operation_name='child', references=child_of(span)) assert span == child def test_tracer_active_span(): tracer = Tracer() assert tracer.active_span is tracer.scope_manager.active.span opentracing-python-2.4.0/tests/test_scope.py000066400000000000000000000062201375552321500212410ustar00rootroot00000000000000# Copyright (c) 2017 The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import mock import types from opentracing.scope_manager import ScopeManager from opentracing.tracer import Tracer from opentracing.scope import Scope from opentracing.span import Span, SpanContext from opentracing import tags from opentracing import logs def test_scope_wrapper(): # ensure `Scope` wraps the `Span` argument span = Span(tracer=Tracer(), context=SpanContext()) scope = Scope(ScopeManager, span) assert scope.span == span def test_scope_context_manager(): # ensure `Scope` can be used in a Context Manager that # calls the `close()` method span = Span(tracer=Tracer(), context=SpanContext()) scope = Scope(ScopeManager(), span) with mock.patch.object(scope, 'close') as close: with scope: pass assert close.call_count == 1 def test_scope_error_report(): tracer = Tracer() scope = tracer.start_active_span('foo') error_message = 'unexpected_situation' with mock.patch.object(scope.span, 'log_kv') as log_kv: with mock.patch.object(scope.span, 'set_tag') as set_tag: try: with scope: raise ValueError(error_message) except ValueError: pass assert set_tag.call_count == 1 assert set_tag.call_args[0] == (tags.ERROR, True) assert log_kv.call_count == 1 log_kv_args = log_kv.call_args[0][0] assert log_kv_args.get(logs.EVENT, None) is tags.ERROR assert log_kv_args.get(logs.MESSAGE, None) is error_message assert log_kv_args.get(logs.ERROR_KIND, None) is ValueError assert isinstance(log_kv_args.get(logs.ERROR_OBJECT, None), ValueError) assert isinstance(log_kv_args.get(logs.STACK, None), types.TracebackType) def test_scope_exit_with_no_span(): # ensure `Scope.__exit__` doesn't fail with `AttributeError` try: with Scope(None, None): raise ValueError except ValueError: pass opentracing-python-2.4.0/tests/test_scope_check_mixin.py000066400000000000000000000053421375552321500236060ustar00rootroot00000000000000# Copyright (c) The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import unittest from opentracing import ScopeManager from opentracing.harness.scope_check import ScopeCompatibilityCheckMixin class VerifyScopeCompatibilityCheck(unittest.TestCase): def test_scope_manager_exception(self): scope_check = ScopeCompatibilityCheckMixin() with self.assertRaises(NotImplementedError): scope_check.scope_manager() def test_missing_active_works(self): scope_check = ScopeCompatibilityCheckMixin() setattr(scope_check, 'scope_manager', lambda: ScopeManager()) with self.assertRaises(AssertionError): scope_check.test_missing_active() with self.assertRaises(AssertionError): scope_check.test_missing_active_external() def test_activate_works(self): scope_check = ScopeCompatibilityCheckMixin() setattr(scope_check, 'scope_manager', lambda: ScopeManager()) with self.assertRaises(AssertionError): scope_check.test_activate() with self.assertRaises(AssertionError): scope_check.test_activate_external() with self.assertRaises(AssertionError): scope_check.test_activate_finish_on_close() with self.assertRaises(AssertionError): scope_check.test_activate_nested() with self.assertRaises(AssertionError): scope_check.test_activate_finish_on_close_nested() def test_close_wrong_order(self): scope_check = ScopeCompatibilityCheckMixin() setattr(scope_check, 'scope_manager', lambda: ScopeManager()) # this test is expected to succeed. scope_check.test_close_wrong_order() opentracing-python-2.4.0/tests/test_scope_manager.py000066400000000000000000000031111375552321500227270ustar00rootroot00000000000000# Copyright (c) 2017 The OpenTracing Authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from opentracing.scope_manager import ScopeManager from opentracing.tracer import Tracer from opentracing.span import Span, SpanContext def test_scope_manager(): # ensure the activation returns the noop `Scope` that is always active scope_manager = ScopeManager() span = Span(tracer=Tracer(), context=SpanContext()) scope = scope_manager.activate(span, False) assert scope == scope_manager._noop_scope assert scope == scope_manager.active opentracing-python-2.4.0/tox.ini000066400000000000000000000002561375552321500166730ustar00rootroot00000000000000[tox] envlist = py27,py35,py36,py37 [testenv] install_command = pip install {opts} {packages} {env:PWD}[tests] whitelist_externals = make commands = make test testbed lint