aiozipkin_1.1.1a1.orig/CHANGES.rst0000644000000000000000000000574614203212452013511 0ustar00CHANGES ======= .. You should *NOT* be adding new change log entries to this file, this file is managed by towncrier. You *may* edit previous change logs to fix problems like typo corrections or such. To add a new change log entry, please see https://pip.pypa.io/en/latest/development/#adding-a-news-entry we named the news folder "changes". WARNING: Don't drop the next directive! .. towncrier release notes start 1.1.1a1 (2021-10-23) ==================== Bugfixes -------- - Fix unhandled AssertionError in aiohttp integration when unknown resource requested by the user. `#400 `_ - Fix ``NoneType`` error when using ``SystemRoute``. `#410 `_ ---- 1.1.0 (2021-05-17) ================== Bugfixes -------- - Expect trace request context to be of SimpleNamespace type. `#385 `_ ---- 1.0.0 (2020-11-06) ================== Bugfixes -------- - Support Python 3.8 and Python 3.9 `#259 `_ ---- 0.7.1 (2020-09-20) ================== Bugfixes -------- - Fix `Manifest.in` file; add `CHANGES.rst` to the Source Tarball. 0.7.0 (2020-07-17) ================== Features -------- - Add support of AWS X-Ray trace id format. `#273 `_ ---- 0.6.0 (2019-10-12) ------------------ * Add context var support for python3.7 aiohttp instrumentation #187 * Single header tracing support #189 * Add retries and batches to transport (thanks @konstantin-stepanov) * Drop python3.5 support #238 * Use new typing syntax in codebase #237 0.5.0 (2018-12-25) ------------------ * More strict typing configuration is used #147 * Fixed bunch of typos in code and docs #151 #153 (thanks @deejay1) * Added interface for Transport #155 (thanks @deejay1) * Added create_custom helper for easer tracer configuration #160 (thanks @deejay1) * Added interface for Sampler #160 (thanks @deejay1) * Added py.typed marker 0.4.0 (2018-07-11) ------------------ * Add more coverage with typing #147 * Breaking change: typo send_inteval => send_interval #144 (thanks @gugu) * Breaking change: do not append api/v2/spans to the zipkin dress #150 0.3.0 (2018-06-13) ------------------ * Add support http.route tag for aiohttp #138 * Make zipkin address builder more permissive #141 (thanks @dsantosfff) 0.2.0 (2018-03-03) ------------------ * Breaking change: az.create is coroutine now #114 * Added context manger for tracer object #114 * Added more mypy types #117 0.1.1 (2018-01-26) ------------------ * Added new_child helper method #83 0.1.0 (2018-01-21) ------------------ After few months of work and beta releases here are basic features: * Initial release. * Implemented zipkin v2 protocol with HTTP transport * Added jaeger support * Added stackdriver support * Added aiohttp server support * Added aiohttp 3.0.0 client tracing support * Added examples and demos aiozipkin_1.1.1a1.orig/LICENSE0000644000000000000000000002605613751206174012724 0ustar00Apache 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 2019 Nikolay Novik 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. aiozipkin_1.1.1a1.orig/MANIFEST.in0000644000000000000000000000014213731614023013433 0ustar00include LICENSE include CHANGES.rst include README.rst graft aiozipkin global-exclude *.pyc *.swp aiozipkin_1.1.1a1.orig/PKG-INFO0000644000000000000000000002451014203212452012772 0ustar00Metadata-Version: 2.1 Name: aiozipkin Version: 1.1.1a1 Summary: Distributed tracing instrumentation for asyncio application with zipkin Home-page: https://github.com/aio-libs/aiozipkin Author: Nikolay Novik Author-email: nickolainovik@gmail.com License: Apache 2 Download-URL: https://pypi.python.org/pypi/aiozipkin Keywords: zipkin,distributed-tracing,tracing Platform: POSIX Classifier: License :: OSI Approved :: MIT License Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Operating System :: POSIX Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: AsyncIO Requires-Python: >=3.6 License-File: LICENSE aiozipkin ========= .. image:: https://github.com/aio-libs/aiozipkin/workflows/CI/badge.svg :target: https://github.com/aio-libs/aiozipkin/actions?query=workflow%3ACI .. image:: https://codecov.io/gh/aio-libs/aiozipkin/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiozipkin .. image:: https://api.codeclimate.com/v1/badges/1ff813d5cad2d702cbf1/maintainability :target: https://codeclimate.com/github/aio-libs/aiozipkin/maintainability :alt: Maintainability .. image:: https://img.shields.io/pypi/v/aiozipkin.svg :target: https://pypi.python.org/pypi/aiozipkin .. image:: https://readthedocs.org/projects/aiozipkin/badge/?version=latest :target: http://aiozipkin.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter **aiozipkin** is Python 3.6+ module that adds distributed tracing capabilities from asyncio_ applications with zipkin (http://zipkin.io) server instrumentation. zipkin_ is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures. It manages both the collection and lookup of this data. Zipkin’s design is based on the Google Dapper paper. Applications are instrumented with **aiozipkin** report timing data to zipkin_. The Zipkin UI also presents a Dependency diagram showing how many traced requests went through each application. If you are troubleshooting latency problems or errors, you can filter or sort all traces based on the application, length of trace, annotation, or timestamp. .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/zipkin_animation2.gif :alt: zipkin ui animation Features ======== * Distributed tracing capabilities to **asyncio** applications. * Support zipkin_ ``v2`` protocol. * Easy to use API. * Explicit context handling, no thread local variables. * Can work with jaeger_ and stackdriver_ through zipkin compatible API. zipkin vocabulary ----------------- Before code lets learn important zipkin_ vocabulary, for more detailed information please visit https://zipkin.io/pages/instrumenting .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/zipkin_glossary.png :alt: zipkin ui glossary * **Span** represents one specific method (RPC) call * **Annotation** string data associated with a particular timestamp in span * **Tag** - key and value associated with given span * **Trace** - collection of spans, related to serving particular request Simple example -------------- .. code:: python import asyncio import aiozipkin as az async def run(): # setup zipkin client zipkin_address = 'http://127.0.0.1:9411/api/v2/spans' endpoint = az.create_endpoint( "simple_service", ipv4="127.0.0.1", port=8080) tracer = await az.create(zipkin_address, endpoint, sample_rate=1.0) # create and setup new trace with tracer.new_trace(sampled=True) as span: # give a name for the span span.name("Slow SQL") # tag with relevant information span.tag("span_type", "root") # indicate that this is client span span.kind(az.CLIENT) # make timestamp and name it with START SQL query span.annotate("START SQL SELECT * FROM") # imitate long SQL query await asyncio.sleep(0.1) # make other timestamp and name it "END SQL" span.annotate("END SQL") await tracer.close() if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(run()) aiohttp example --------------- *aiozipkin* includes *aiohttp* server instrumentation, for this create `web.Application()` as usual and install aiozipkin plugin: .. code:: python import aiozipkin as az def init_app(): host, port = "127.0.0.1", 8080 app = web.Application() endpoint = az.create_endpoint("AIOHTTP_SERVER", ipv4=host, port=port) tracer = await az.create(zipkin_address, endpoint, sample_rate=1.0) az.setup(app, tracer) That is it, plugin adds middleware that tries to fetch context from headers, and create/join new trace. Optionally on client side you can add propagation headers in order to force tracing and to see network latency between client and server. .. code:: python import aiozipkin as az endpoint = az.create_endpoint("AIOHTTP_CLIENT") tracer = await az.create(zipkin_address, endpoint) with tracer.new_trace() as span: span.kind(az.CLIENT) headers = span.context.make_headers() host = "http://127.0.0.1:8080/api/v1/posts/{}".format(i) resp = await session.get(host, headers=headers) await resp.text() Documentation ------------- http://aiozipkin.readthedocs.io/ Installation ------------ Installation process is simple, just:: $ pip install aiozipkin Support of other collectors =========================== **aiozipkin** can work with any other zipkin_ compatible service, currently we tested it with jaeger_ and stackdriver_. Jaeger support -------------- jaeger_ supports zipkin_ span format as result it is possible to use *aiozipkin* with jaeger_ server. You just need to specify *jaeger* server address and it should work out of the box. Not need to run local zipkin server. For more informations see tests and jaeger_ documentation. .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/jaeger.png :alt: jaeger ui animation Stackdriver support ------------------- Google stackdriver_ supports zipkin_ span format as result it is possible to use *aiozipkin* with this google_ service. In order to make this work you need to setup zipkin service locally, that will send trace to the cloud. See google_ cloud documentation how to setup make zipkin collector: .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/stackdriver.png :alt: jaeger ui animation Requirements ------------ * Python_ 3.6+ * aiohttp_ .. _PEP492: https://www.python.org/dev/peps/pep-0492/ .. _Python: https://www.python.org .. _aiohttp: https://github.com/KeepSafe/aiohttp .. _asyncio: http://docs.python.org/3.5/library/asyncio.html .. _uvloop: https://github.com/MagicStack/uvloop .. _zipkin: http://zipkin.io .. _jaeger: http://jaeger.readthedocs.io/en/latest/ .. _stackdriver: https://cloud.google.com/stackdriver/ .. _google: https://cloud.google.com/trace/docs/zipkin CHANGES ======= .. You should *NOT* be adding new change log entries to this file, this file is managed by towncrier. You *may* edit previous change logs to fix problems like typo corrections or such. To add a new change log entry, please see https://pip.pypa.io/en/latest/development/#adding-a-news-entry we named the news folder "changes". WARNING: Don't drop the next directive! .. towncrier release notes start 1.1.1a1 (2021-10-23) ==================== Bugfixes -------- - Fix unhandled AssertionError in aiohttp integration when unknown resource requested by the user. `#400 `_ - Fix ``NoneType`` error when using ``SystemRoute``. `#410 `_ ---- 1.1.0 (2021-05-17) ================== Bugfixes -------- - Expect trace request context to be of SimpleNamespace type. `#385 `_ ---- 1.0.0 (2020-11-06) ================== Bugfixes -------- - Support Python 3.8 and Python 3.9 `#259 `_ ---- 0.7.1 (2020-09-20) ================== Bugfixes -------- - Fix `Manifest.in` file; add `CHANGES.rst` to the Source Tarball. 0.7.0 (2020-07-17) ================== Features -------- - Add support of AWS X-Ray trace id format. `#273 `_ ---- 0.6.0 (2019-10-12) ------------------ * Add context var support for python3.7 aiohttp instrumentation #187 * Single header tracing support #189 * Add retries and batches to transport (thanks @konstantin-stepanov) * Drop python3.5 support #238 * Use new typing syntax in codebase #237 0.5.0 (2018-12-25) ------------------ * More strict typing configuration is used #147 * Fixed bunch of typos in code and docs #151 #153 (thanks @deejay1) * Added interface for Transport #155 (thanks @deejay1) * Added create_custom helper for easer tracer configuration #160 (thanks @deejay1) * Added interface for Sampler #160 (thanks @deejay1) * Added py.typed marker 0.4.0 (2018-07-11) ------------------ * Add more coverage with typing #147 * Breaking change: typo send_inteval => send_interval #144 (thanks @gugu) * Breaking change: do not append api/v2/spans to the zipkin dress #150 0.3.0 (2018-06-13) ------------------ * Add support http.route tag for aiohttp #138 * Make zipkin address builder more permissive #141 (thanks @dsantosfff) 0.2.0 (2018-03-03) ------------------ * Breaking change: az.create is coroutine now #114 * Added context manger for tracer object #114 * Added more mypy types #117 0.1.1 (2018-01-26) ------------------ * Added new_child helper method #83 0.1.0 (2018-01-21) ------------------ After few months of work and beta releases here are basic features: * Initial release. * Implemented zipkin v2 protocol with HTTP transport * Added jaeger support * Added stackdriver support * Added aiohttp server support * Added aiohttp 3.0.0 client tracing support * Added examples and demos aiozipkin_1.1.1a1.orig/README.rst0000644000000000000000000001474313751215010013373 0ustar00aiozipkin ========= .. image:: https://github.com/aio-libs/aiozipkin/workflows/CI/badge.svg :target: https://github.com/aio-libs/aiozipkin/actions?query=workflow%3ACI .. image:: https://codecov.io/gh/aio-libs/aiozipkin/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiozipkin .. image:: https://api.codeclimate.com/v1/badges/1ff813d5cad2d702cbf1/maintainability :target: https://codeclimate.com/github/aio-libs/aiozipkin/maintainability :alt: Maintainability .. image:: https://img.shields.io/pypi/v/aiozipkin.svg :target: https://pypi.python.org/pypi/aiozipkin .. image:: https://readthedocs.org/projects/aiozipkin/badge/?version=latest :target: http://aiozipkin.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter **aiozipkin** is Python 3.6+ module that adds distributed tracing capabilities from asyncio_ applications with zipkin (http://zipkin.io) server instrumentation. zipkin_ is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures. It manages both the collection and lookup of this data. Zipkin’s design is based on the Google Dapper paper. Applications are instrumented with **aiozipkin** report timing data to zipkin_. The Zipkin UI also presents a Dependency diagram showing how many traced requests went through each application. If you are troubleshooting latency problems or errors, you can filter or sort all traces based on the application, length of trace, annotation, or timestamp. .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/zipkin_animation2.gif :alt: zipkin ui animation Features ======== * Distributed tracing capabilities to **asyncio** applications. * Support zipkin_ ``v2`` protocol. * Easy to use API. * Explicit context handling, no thread local variables. * Can work with jaeger_ and stackdriver_ through zipkin compatible API. zipkin vocabulary ----------------- Before code lets learn important zipkin_ vocabulary, for more detailed information please visit https://zipkin.io/pages/instrumenting .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/zipkin_glossary.png :alt: zipkin ui glossary * **Span** represents one specific method (RPC) call * **Annotation** string data associated with a particular timestamp in span * **Tag** - key and value associated with given span * **Trace** - collection of spans, related to serving particular request Simple example -------------- .. code:: python import asyncio import aiozipkin as az async def run(): # setup zipkin client zipkin_address = 'http://127.0.0.1:9411/api/v2/spans' endpoint = az.create_endpoint( "simple_service", ipv4="127.0.0.1", port=8080) tracer = await az.create(zipkin_address, endpoint, sample_rate=1.0) # create and setup new trace with tracer.new_trace(sampled=True) as span: # give a name for the span span.name("Slow SQL") # tag with relevant information span.tag("span_type", "root") # indicate that this is client span span.kind(az.CLIENT) # make timestamp and name it with START SQL query span.annotate("START SQL SELECT * FROM") # imitate long SQL query await asyncio.sleep(0.1) # make other timestamp and name it "END SQL" span.annotate("END SQL") await tracer.close() if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(run()) aiohttp example --------------- *aiozipkin* includes *aiohttp* server instrumentation, for this create `web.Application()` as usual and install aiozipkin plugin: .. code:: python import aiozipkin as az def init_app(): host, port = "127.0.0.1", 8080 app = web.Application() endpoint = az.create_endpoint("AIOHTTP_SERVER", ipv4=host, port=port) tracer = await az.create(zipkin_address, endpoint, sample_rate=1.0) az.setup(app, tracer) That is it, plugin adds middleware that tries to fetch context from headers, and create/join new trace. Optionally on client side you can add propagation headers in order to force tracing and to see network latency between client and server. .. code:: python import aiozipkin as az endpoint = az.create_endpoint("AIOHTTP_CLIENT") tracer = await az.create(zipkin_address, endpoint) with tracer.new_trace() as span: span.kind(az.CLIENT) headers = span.context.make_headers() host = "http://127.0.0.1:8080/api/v1/posts/{}".format(i) resp = await session.get(host, headers=headers) await resp.text() Documentation ------------- http://aiozipkin.readthedocs.io/ Installation ------------ Installation process is simple, just:: $ pip install aiozipkin Support of other collectors =========================== **aiozipkin** can work with any other zipkin_ compatible service, currently we tested it with jaeger_ and stackdriver_. Jaeger support -------------- jaeger_ supports zipkin_ span format as result it is possible to use *aiozipkin* with jaeger_ server. You just need to specify *jaeger* server address and it should work out of the box. Not need to run local zipkin server. For more informations see tests and jaeger_ documentation. .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/jaeger.png :alt: jaeger ui animation Stackdriver support ------------------- Google stackdriver_ supports zipkin_ span format as result it is possible to use *aiozipkin* with this google_ service. In order to make this work you need to setup zipkin service locally, that will send trace to the cloud. See google_ cloud documentation how to setup make zipkin collector: .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/stackdriver.png :alt: jaeger ui animation Requirements ------------ * Python_ 3.6+ * aiohttp_ .. _PEP492: https://www.python.org/dev/peps/pep-0492/ .. _Python: https://www.python.org .. _aiohttp: https://github.com/KeepSafe/aiohttp .. _asyncio: http://docs.python.org/3.5/library/asyncio.html .. _uvloop: https://github.com/MagicStack/uvloop .. _zipkin: http://zipkin.io .. _jaeger: http://jaeger.readthedocs.io/en/latest/ .. _stackdriver: https://cloud.google.com/stackdriver/ .. _google: https://cloud.google.com/trace/docs/zipkin aiozipkin_1.1.1a1.orig/aiozipkin.egg-info/0000755000000000000000000000000014203212452015362 5ustar00aiozipkin_1.1.1a1.orig/aiozipkin/0000755000000000000000000000000013164244744013706 5ustar00aiozipkin_1.1.1a1.orig/pyproject.toml0000644000000000000000000000037013704373137014624 0ustar00[tool.towncrier] package = "aiozipkin" filename = "CHANGES.rst" directory = "CHANGES/" title_format = "{version} ({project_date})" template = "CHANGES/.TEMPLATE.rst" issue_format = "`#{issue} `_" aiozipkin_1.1.1a1.orig/setup.cfg0000644000000000000000000000071114203212452013513 0ustar00[flake8] exclude = .git,.env,__pycache__,.eggs max-line-length = 88 ignore = N801,N802,N803,E252,W503,E133,E203,F541,B101 [isort] line_length = 88 include_trailing_comma = True multi_line_output = 3 force_grid_wrap = 0 combine_as_imports = True lines_after_imports = 2 [mypy-async_generator] ignore_missing_imports = True [mypy-pytest] ignore_missing_imports = True [mypy-setuptools] ignore_missing_imports = True [egg_info] tag_build = tag_date = 0 aiozipkin_1.1.1a1.orig/setup.py0000644000000000000000000000364014047173457013431 0ustar00import os import re import sys from setuptools import find_packages, setup # type: ignore if sys.version_info < (3, 6, 0): raise RuntimeError("aiozipkin does not support Python earlier than 3.6.0") def read(f: str) -> str: return open(os.path.join(os.path.dirname(__file__), f)).read().strip() install_requires = ["aiohttp>=3.7.2"] def read_version() -> str: regexp = re.compile(r'^__version__\W*=\W*"([\d.abrc]+)"') init_py = os.path.join(os.path.dirname(__file__), "aiozipkin", "__init__.py") with open(init_py) as f: for line in f: match = regexp.match(line) if match is not None: return match.group(1) else: msg = "Cannot find version in aiozipkin/__init__.py" raise RuntimeError(msg) classifiers = [ "License :: OSI Approved :: MIT License", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Operating System :: POSIX", "Development Status :: 5 - Production/Stable", "Framework :: AsyncIO", ] setup( name="aiozipkin", version=read_version(), description=( "Distributed tracing instrumentation " "for asyncio application with zipkin" ), long_description="\n\n".join((read("README.rst"), read("CHANGES.rst"))), classifiers=classifiers, platforms=["POSIX"], author="Nikolay Novik", author_email="nickolainovik@gmail.com", url="https://github.com/aio-libs/aiozipkin", download_url="https://pypi.python.org/pypi/aiozipkin", license="Apache 2", packages=find_packages(), python_requires=">=3.6", install_requires=install_requires, keywords=["zipkin", "distributed-tracing", "tracing"], zip_safe=True, include_package_data=True, ) aiozipkin_1.1.1a1.orig/aiozipkin.egg-info/PKG-INFO0000644000000000000000000002451014203212452016461 0ustar00Metadata-Version: 2.1 Name: aiozipkin Version: 1.1.1a1 Summary: Distributed tracing instrumentation for asyncio application with zipkin Home-page: https://github.com/aio-libs/aiozipkin Author: Nikolay Novik Author-email: nickolainovik@gmail.com License: Apache 2 Download-URL: https://pypi.python.org/pypi/aiozipkin Keywords: zipkin,distributed-tracing,tracing Platform: POSIX Classifier: License :: OSI Approved :: MIT License Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Operating System :: POSIX Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: AsyncIO Requires-Python: >=3.6 License-File: LICENSE aiozipkin ========= .. image:: https://github.com/aio-libs/aiozipkin/workflows/CI/badge.svg :target: https://github.com/aio-libs/aiozipkin/actions?query=workflow%3ACI .. image:: https://codecov.io/gh/aio-libs/aiozipkin/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiozipkin .. image:: https://api.codeclimate.com/v1/badges/1ff813d5cad2d702cbf1/maintainability :target: https://codeclimate.com/github/aio-libs/aiozipkin/maintainability :alt: Maintainability .. image:: https://img.shields.io/pypi/v/aiozipkin.svg :target: https://pypi.python.org/pypi/aiozipkin .. image:: https://readthedocs.org/projects/aiozipkin/badge/?version=latest :target: http://aiozipkin.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter **aiozipkin** is Python 3.6+ module that adds distributed tracing capabilities from asyncio_ applications with zipkin (http://zipkin.io) server instrumentation. zipkin_ is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures. It manages both the collection and lookup of this data. Zipkin’s design is based on the Google Dapper paper. Applications are instrumented with **aiozipkin** report timing data to zipkin_. The Zipkin UI also presents a Dependency diagram showing how many traced requests went through each application. If you are troubleshooting latency problems or errors, you can filter or sort all traces based on the application, length of trace, annotation, or timestamp. .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/zipkin_animation2.gif :alt: zipkin ui animation Features ======== * Distributed tracing capabilities to **asyncio** applications. * Support zipkin_ ``v2`` protocol. * Easy to use API. * Explicit context handling, no thread local variables. * Can work with jaeger_ and stackdriver_ through zipkin compatible API. zipkin vocabulary ----------------- Before code lets learn important zipkin_ vocabulary, for more detailed information please visit https://zipkin.io/pages/instrumenting .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/zipkin_glossary.png :alt: zipkin ui glossary * **Span** represents one specific method (RPC) call * **Annotation** string data associated with a particular timestamp in span * **Tag** - key and value associated with given span * **Trace** - collection of spans, related to serving particular request Simple example -------------- .. code:: python import asyncio import aiozipkin as az async def run(): # setup zipkin client zipkin_address = 'http://127.0.0.1:9411/api/v2/spans' endpoint = az.create_endpoint( "simple_service", ipv4="127.0.0.1", port=8080) tracer = await az.create(zipkin_address, endpoint, sample_rate=1.0) # create and setup new trace with tracer.new_trace(sampled=True) as span: # give a name for the span span.name("Slow SQL") # tag with relevant information span.tag("span_type", "root") # indicate that this is client span span.kind(az.CLIENT) # make timestamp and name it with START SQL query span.annotate("START SQL SELECT * FROM") # imitate long SQL query await asyncio.sleep(0.1) # make other timestamp and name it "END SQL" span.annotate("END SQL") await tracer.close() if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(run()) aiohttp example --------------- *aiozipkin* includes *aiohttp* server instrumentation, for this create `web.Application()` as usual and install aiozipkin plugin: .. code:: python import aiozipkin as az def init_app(): host, port = "127.0.0.1", 8080 app = web.Application() endpoint = az.create_endpoint("AIOHTTP_SERVER", ipv4=host, port=port) tracer = await az.create(zipkin_address, endpoint, sample_rate=1.0) az.setup(app, tracer) That is it, plugin adds middleware that tries to fetch context from headers, and create/join new trace. Optionally on client side you can add propagation headers in order to force tracing and to see network latency between client and server. .. code:: python import aiozipkin as az endpoint = az.create_endpoint("AIOHTTP_CLIENT") tracer = await az.create(zipkin_address, endpoint) with tracer.new_trace() as span: span.kind(az.CLIENT) headers = span.context.make_headers() host = "http://127.0.0.1:8080/api/v1/posts/{}".format(i) resp = await session.get(host, headers=headers) await resp.text() Documentation ------------- http://aiozipkin.readthedocs.io/ Installation ------------ Installation process is simple, just:: $ pip install aiozipkin Support of other collectors =========================== **aiozipkin** can work with any other zipkin_ compatible service, currently we tested it with jaeger_ and stackdriver_. Jaeger support -------------- jaeger_ supports zipkin_ span format as result it is possible to use *aiozipkin* with jaeger_ server. You just need to specify *jaeger* server address and it should work out of the box. Not need to run local zipkin server. For more informations see tests and jaeger_ documentation. .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/jaeger.png :alt: jaeger ui animation Stackdriver support ------------------- Google stackdriver_ supports zipkin_ span format as result it is possible to use *aiozipkin* with this google_ service. In order to make this work you need to setup zipkin service locally, that will send trace to the cloud. See google_ cloud documentation how to setup make zipkin collector: .. image:: https://raw.githubusercontent.com/aio-libs/aiozipkin/master/docs/stackdriver.png :alt: jaeger ui animation Requirements ------------ * Python_ 3.6+ * aiohttp_ .. _PEP492: https://www.python.org/dev/peps/pep-0492/ .. _Python: https://www.python.org .. _aiohttp: https://github.com/KeepSafe/aiohttp .. _asyncio: http://docs.python.org/3.5/library/asyncio.html .. _uvloop: https://github.com/MagicStack/uvloop .. _zipkin: http://zipkin.io .. _jaeger: http://jaeger.readthedocs.io/en/latest/ .. _stackdriver: https://cloud.google.com/stackdriver/ .. _google: https://cloud.google.com/trace/docs/zipkin CHANGES ======= .. You should *NOT* be adding new change log entries to this file, this file is managed by towncrier. You *may* edit previous change logs to fix problems like typo corrections or such. To add a new change log entry, please see https://pip.pypa.io/en/latest/development/#adding-a-news-entry we named the news folder "changes". WARNING: Don't drop the next directive! .. towncrier release notes start 1.1.1a1 (2021-10-23) ==================== Bugfixes -------- - Fix unhandled AssertionError in aiohttp integration when unknown resource requested by the user. `#400 `_ - Fix ``NoneType`` error when using ``SystemRoute``. `#410 `_ ---- 1.1.0 (2021-05-17) ================== Bugfixes -------- - Expect trace request context to be of SimpleNamespace type. `#385 `_ ---- 1.0.0 (2020-11-06) ================== Bugfixes -------- - Support Python 3.8 and Python 3.9 `#259 `_ ---- 0.7.1 (2020-09-20) ================== Bugfixes -------- - Fix `Manifest.in` file; add `CHANGES.rst` to the Source Tarball. 0.7.0 (2020-07-17) ================== Features -------- - Add support of AWS X-Ray trace id format. `#273 `_ ---- 0.6.0 (2019-10-12) ------------------ * Add context var support for python3.7 aiohttp instrumentation #187 * Single header tracing support #189 * Add retries and batches to transport (thanks @konstantin-stepanov) * Drop python3.5 support #238 * Use new typing syntax in codebase #237 0.5.0 (2018-12-25) ------------------ * More strict typing configuration is used #147 * Fixed bunch of typos in code and docs #151 #153 (thanks @deejay1) * Added interface for Transport #155 (thanks @deejay1) * Added create_custom helper for easer tracer configuration #160 (thanks @deejay1) * Added interface for Sampler #160 (thanks @deejay1) * Added py.typed marker 0.4.0 (2018-07-11) ------------------ * Add more coverage with typing #147 * Breaking change: typo send_inteval => send_interval #144 (thanks @gugu) * Breaking change: do not append api/v2/spans to the zipkin dress #150 0.3.0 (2018-06-13) ------------------ * Add support http.route tag for aiohttp #138 * Make zipkin address builder more permissive #141 (thanks @dsantosfff) 0.2.0 (2018-03-03) ------------------ * Breaking change: az.create is coroutine now #114 * Added context manger for tracer object #114 * Added more mypy types #117 0.1.1 (2018-01-26) ------------------ * Added new_child helper method #83 0.1.0 (2018-01-21) ------------------ After few months of work and beta releases here are basic features: * Initial release. * Implemented zipkin v2 protocol with HTTP transport * Added jaeger support * Added stackdriver support * Added aiohttp server support * Added aiohttp 3.0.0 client tracing support * Added examples and demos aiozipkin_1.1.1a1.orig/aiozipkin.egg-info/SOURCES.txt0000644000000000000000000000107614203212452017252 0ustar00CHANGES.rst LICENSE MANIFEST.in README.rst pyproject.toml setup.cfg setup.py aiozipkin/__init__.py aiozipkin/aiohttp_helpers.py aiozipkin/constants.py aiozipkin/context_managers.py aiozipkin/helpers.py aiozipkin/log.py aiozipkin/mypy_types.py aiozipkin/py.typed aiozipkin/record.py aiozipkin/sampler.py aiozipkin/span.py aiozipkin/tracer.py aiozipkin/transport.py aiozipkin/utils.py aiozipkin.egg-info/PKG-INFO aiozipkin.egg-info/SOURCES.txt aiozipkin.egg-info/dependency_links.txt aiozipkin.egg-info/requires.txt aiozipkin.egg-info/top_level.txt aiozipkin.egg-info/zip-safeaiozipkin_1.1.1a1.orig/aiozipkin.egg-info/dependency_links.txt0000644000000000000000000000000114203212452021430 0ustar00 aiozipkin_1.1.1a1.orig/aiozipkin.egg-info/requires.txt0000644000000000000000000000001714203212452017760 0ustar00aiohttp>=3.7.2 aiozipkin_1.1.1a1.orig/aiozipkin.egg-info/top_level.txt0000644000000000000000000000001214203212452020105 0ustar00aiozipkin aiozipkin_1.1.1a1.orig/aiozipkin.egg-info/zip-safe0000644000000000000000000000000114203212452017012 0ustar00 aiozipkin_1.1.1a1.orig/aiozipkin/__init__.py0000644000000000000000000000216414203212452016004 0ustar00from .aiohttp_helpers import ( APP_AIOZIPKIN_KEY, REQUEST_AIOZIPKIN_KEY, get_tracer, make_trace_config, middleware_maker, request_span, setup, ) from .constants import ( HTTP_HOST, HTTP_METHOD, HTTP_PATH, HTTP_REQUEST_SIZE, HTTP_RESPONSE_SIZE, HTTP_ROUTE, HTTP_STATUS_CODE, HTTP_URL, ) from .helpers import CLIENT, CONSUMER, PRODUCER, SERVER, create_endpoint, make_context from .sampler import Sampler from .span import SpanAbc from .tracer import Tracer, create, create_custom __version__ = "1.1.1a1" __all__ = ( "Tracer", "Sampler", "SpanAbc", "create", "create_custom", "create_endpoint", "make_context", # aiohttp helpers "setup", "get_tracer", "request_span", "middleware_maker", "make_trace_config", "APP_AIOZIPKIN_KEY", "REQUEST_AIOZIPKIN_KEY", # possible span kinds "CLIENT", "SERVER", "PRODUCER", "CONSUMER", # constants "HTTP_HOST", "HTTP_METHOD", "HTTP_PATH", "HTTP_REQUEST_SIZE", "HTTP_RESPONSE_SIZE", "HTTP_STATUS_CODE", "HTTP_URL", "HTTP_ROUTE", ) aiozipkin_1.1.1a1.orig/aiozipkin/aiohttp_helpers.py0000644000000000000000000002320714115413203017437 0ustar00import ipaddress import sys from contextlib import contextmanager from types import SimpleNamespace from typing import ( Any, Awaitable, Callable, Dict, Generator, Iterable, Optional, Set, cast, ) import aiohttp from aiohttp import ( TraceRequestEndParams, TraceRequestExceptionParams, TraceRequestStartParams, ) from aiohttp.web import ( AbstractRoute, Application, HTTPException, Request, StreamResponse, middleware, ) from .constants import HTTP_METHOD, HTTP_PATH, HTTP_ROUTE, HTTP_STATUS_CODE from .helpers import ( CLIENT, SERVER, TraceContext, make_context, parse_debug_header, parse_sampled_header, ) from .span import SpanAbc from .tracer import Tracer APP_AIOZIPKIN_KEY = "aiozipkin_tracer" REQUEST_AIOZIPKIN_KEY = "aiozipkin_span" __all__ = ( "setup", "get_tracer", "request_span", "middleware_maker", "make_trace_config", "APP_AIOZIPKIN_KEY", "REQUEST_AIOZIPKIN_KEY", ) Handler = Callable[[Request], Awaitable[StreamResponse]] Middleware = Callable[[Request, Handler], Awaitable[StreamResponse]] def _set_remote_endpoint(span: SpanAbc, request: Request) -> None: peername = request.remote if peername is not None: kwargs: Dict[str, Any] = {} try: peer_ipaddress = ipaddress.ip_address(peername) except ValueError: pass else: if isinstance(peer_ipaddress, ipaddress.IPv4Address): kwargs["ipv4"] = str(peer_ipaddress) else: kwargs["ipv6"] = str(peer_ipaddress) if kwargs: span.remote_endpoint(None, **kwargs) def _get_span(request: Request, tracer: Tracer) -> SpanAbc: # builds span from incoming request, if no context found, create # new span context = make_context(request.headers) if context is None: sampled = parse_sampled_header(request.headers) debug = parse_debug_header(request.headers) span = tracer.new_trace(sampled=sampled, debug=debug) else: span = tracer.join_span(context) return span def _set_span_properties(span: SpanAbc, request: Request) -> None: span_name = f"{request.method.upper()} {request.path}" span.name(span_name) span.kind(SERVER) span.tag(HTTP_PATH, request.path) span.tag(HTTP_METHOD, request.method.upper()) resource = request.match_info.route.resource if resource is not None: route = resource.canonical span.tag(HTTP_ROUTE, route) _set_remote_endpoint(span, request) PY37 = sys.version_info >= (3, 7) if PY37: from contextvars import ContextVar OptTraceVar = ContextVar[Optional[TraceContext]] zipkin_context: OptTraceVar = ContextVar("zipkin_context", default=None) @contextmanager def set_context_value( context_var: OptTraceVar, value: TraceContext ) -> Generator[OptTraceVar, None, None]: token = context_var.set(value) try: yield context_var finally: context_var.reset(token) def middleware_maker( skip_routes: Optional[Iterable[AbstractRoute]] = None, tracer_key: str = APP_AIOZIPKIN_KEY, request_key: str = REQUEST_AIOZIPKIN_KEY, ) -> Middleware: s = skip_routes skip_routes_set: Set[AbstractRoute] = set(s) if s else set() @middleware async def aiozipkin_middleware( request: Request, handler: Handler ) -> StreamResponse: # route is in skip list, we do not track anything with zipkin if request.match_info.route in skip_routes_set: resp = await handler(request) return resp tracer = request.app[tracer_key] span = _get_span(request, tracer) request[request_key] = span if span.is_noop: resp = await handler(request) return resp if PY37: with set_context_value(zipkin_context, span.context): with span: _set_span_properties(span, request) try: resp = await handler(request) except HTTPException as e: span.tag(HTTP_STATUS_CODE, str(e.status)) raise span.tag(HTTP_STATUS_CODE, str(resp.status)) else: with span: _set_span_properties(span, request) try: resp = await handler(request) except HTTPException as e: span.tag(HTTP_STATUS_CODE, str(e.status)) raise span.tag(HTTP_STATUS_CODE, str(resp.status)) return resp return aiozipkin_middleware def setup( app: Application, tracer: Tracer, *, skip_routes: Optional[Iterable[AbstractRoute]] = None, tracer_key: str = APP_AIOZIPKIN_KEY, request_key: str = REQUEST_AIOZIPKIN_KEY, ) -> Application: """Sets required parameters in aiohttp applications for aiozipkin. Tracer added into application context and cleaned after application shutdown. You can provide custom tracer_key, if default name is not suitable. """ app[tracer_key] = tracer m = middleware_maker( skip_routes=skip_routes, tracer_key=tracer_key, request_key=request_key ) app.middlewares.append(m) # register cleanup signal to close zipkin transport connections async def close_aiozipkin(app: Application) -> None: await app[tracer_key].close() app.on_cleanup.append(close_aiozipkin) return app def get_tracer(app: Application, tracer_key: str = APP_AIOZIPKIN_KEY) -> Tracer: """Returns tracer object from application context. By default tracer has APP_AIOZIPKIN_KEY in aiohttp application context, you can provide own key, if for some reason default one is not suitable. """ return cast(Tracer, app[tracer_key]) def request_span(request: Request, request_key: str = REQUEST_AIOZIPKIN_KEY) -> SpanAbc: """Returns span created by middleware from request context, you can use it as parent on next child span. """ return cast(SpanAbc, request[request_key]) class ZipkinClientSignals: """Class contains signal handler for aiohttp client. Handlers executed only if aiohttp session contains tracer context with span. """ def __init__(self, tracer: Tracer) -> None: self._tracer = tracer def _get_span_context( self, trace_config_ctx: SimpleNamespace ) -> Optional[TraceContext]: ctx = self._get_span_context_from_dict( trace_config_ctx ) or self._get_span_context_from_namespace(trace_config_ctx) if ctx: return ctx if PY37: has_implicit_context = zipkin_context.get() is not None if has_implicit_context: return zipkin_context.get() return None def _get_span_context_from_dict( self, trace_config_ctx: SimpleNamespace ) -> Optional[TraceContext]: ctx = trace_config_ctx.trace_request_ctx if isinstance(ctx, dict): r: Optional[TraceContext] = ctx.get("span_context") return r return None def _get_span_context_from_namespace( self, trace_config_ctx: SimpleNamespace ) -> Optional[TraceContext]: ctx = trace_config_ctx.trace_request_ctx if isinstance(ctx, SimpleNamespace): r: Optional[TraceContext] = getattr(ctx, "span_context", None) return r return None async def on_request_start( self, session: aiohttp.ClientSession, context: SimpleNamespace, params: TraceRequestStartParams, ) -> None: span_context = self._get_span_context(context) if span_context is None: return p = params span = self._tracer.new_child(span_context) context._span = span span.start() span_name = f"client {p.method.upper()} {p.url.path}" span.name(span_name) span.kind(CLIENT) ctx = context.trace_request_ctx propagate_headers = True if isinstance(ctx, dict): # Check ctx is dict to be compatible with old package versions propagate_headers = ctx.get("propagate_headers", True) if isinstance(ctx, SimpleNamespace): propagate_headers = getattr(ctx, "propagate_headers", True) if propagate_headers: span_headers = span.context.make_headers() p.headers.update(span_headers) async def on_request_end( self, session: aiohttp.ClientSession, context: SimpleNamespace, params: TraceRequestEndParams, ) -> None: span_context = self._get_span_context(context) if span_context is None: return span = context._span span.finish() delattr(context, "_span") async def on_request_exception( self, session: aiohttp.ClientSession, context: SimpleNamespace, params: TraceRequestExceptionParams, ) -> None: span_context = self._get_span_context(context) if span_context is None: return span = context._span span.finish(exception=params.exception) delattr(context, "_span") def make_trace_config(tracer: Tracer) -> aiohttp.TraceConfig: """Creates aiohttp.TraceConfig with enabled aiozipking instrumentation for aiohttp client. """ trace_config = aiohttp.TraceConfig() zipkin = ZipkinClientSignals(tracer) trace_config.on_request_start.append(zipkin.on_request_start) trace_config.on_request_end.append(zipkin.on_request_end) trace_config.on_request_exception.append(zipkin.on_request_exception) return trace_config aiozipkin_1.1.1a1.orig/aiozipkin/constants.py0000644000000000000000000000057413704354533016300 0ustar00# standard tags (binary annotations) HTTP_HOST = "http.host" HTTP_METHOD = "http.method" HTTP_PATH = "http.path" HTTP_REQUEST_SIZE = "http.request.size" HTTP_RESPONSE_SIZE = "http.response.size" HTTP_STATUS_CODE = "http.status_code" HTTP_URL = "http.url" HTTP_ROUTE = "http.route" ERROR = "error" LOCAL_COMPONENT = "lc" CLIENT_ADDR = "ca" MESSAGE_ADDR = "ma" SERVER_ADDR = "sa" aiozipkin_1.1.1a1.orig/aiozipkin/context_managers.py0000644000000000000000000000241213704354533017616 0ustar00from collections.abc import Awaitable as AbcAwaitable from types import TracebackType from typing import ( TYPE_CHECKING, Any, AsyncContextManager, Awaitable, Generator, Generic, Optional, Type, TypeVar, ) T = TypeVar("T", bound=AsyncContextManager["T"]) # type: ignore if TYPE_CHECKING: class _Base(AsyncContextManager[T], AbcAwaitable[T]): pass else: class _Base(Generic[T], AsyncContextManager, AbcAwaitable): pass class _ContextManager(_Base[T]): __slots__ = ("_coro", "_obj") def __init__(self, coro: Awaitable[T]) -> None: super().__init__() self._coro: Awaitable[T] = coro self._obj: Optional[T] = None def __await__(self) -> Generator[Any, None, T]: return self._coro.__await__() async def __aenter__(self) -> T: self._obj = await self._coro t: T = await self._obj.__aenter__() return t async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc: Optional[BaseException], tb: Optional[TracebackType], ) -> Optional[bool]: if self._obj is None: raise RuntimeError("__aexit__ called before __aenter__") return await self._obj.__aexit__(exc_type, exc, tb) aiozipkin_1.1.1a1.orig/aiozipkin/helpers.py0000644000000000000000000001416614047173457015735 0ustar00import time from typing import Any, Dict, List, NamedTuple, Optional from .mypy_types import Headers, OptBool, OptInt, OptStr, OptTs # possible span kinds CLIENT = "CLIENT" SERVER = "SERVER" PRODUCER = "PRODUCER" CONSUMER = "CONSUMER" # zipkin headers, for more information see: # https://github.com/openzipkin/b3-propagation TRACE_ID_HEADER = "X-B3-TraceId" SPAN_ID_HEADER = "X-B3-SpanId" PARENT_ID_HEADER = "X-B3-ParentSpanId" FLAGS_HEADER = "X-B3-Flags" SAMPLED_ID_HEADER = "X-B3-Sampled" SINGLE_HEADER = "b3" DELIMITER = "-" DEBUG_MARKER = "d" class _TraceContext(NamedTuple): trace_id: str parent_id: OptStr span_id: str sampled: OptBool debug: bool shared: bool class TraceContext(_TraceContext): """Immutable class with trace related data that travels across process boundaries. """ def make_headers(self) -> Headers: """Creates dict with zipkin headers from available context. Resulting dict should be passed to HTTP client propagate contest to other services. """ return make_headers(self) def make_single_header(self) -> Headers: return make_single_header(self) class Endpoint(NamedTuple): serviceName: OptStr ipv4: OptStr ipv6: OptStr port: OptInt def create_endpoint( service_name: str, *, ipv4: OptStr = None, ipv6: OptStr = None, port: OptInt = None ) -> Endpoint: """Factory function to create Endpoint object.""" return Endpoint(service_name, ipv4, ipv6, port) def make_timestamp(ts: OptTs = None) -> int: """Create zipkin timestamp in microseconds, or convert available one from second. Useful when user supplies ts from time.time() call. """ ts = ts if ts is not None else time.time() return int(ts * 1000 * 1000) # microseconds def make_headers(context: TraceContext) -> Headers: """Creates dict with zipkin headers from supplied trace context.""" headers = { TRACE_ID_HEADER: context.trace_id, SPAN_ID_HEADER: context.span_id, FLAGS_HEADER: "0", SAMPLED_ID_HEADER: "1" if context.sampled else "0", } if context.parent_id is not None: headers[PARENT_ID_HEADER] = context.parent_id return headers def make_single_header(context: TraceContext) -> Headers: """Creates dict with zipkin single header format.""" # b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} c = context # encode sampled flag if c.debug: sampled = "d" elif c.sampled: sampled = "1" else: sampled = "0" params: List[str] = [c.trace_id, c.span_id, sampled] if c.parent_id is not None: params.append(c.parent_id) h = DELIMITER.join(params) headers = {SINGLE_HEADER: h} return headers def parse_sampled_header(headers: Headers) -> OptBool: sampled = headers.get(SAMPLED_ID_HEADER.lower(), None) if sampled is None or sampled == "": return None return True if sampled == "1" else False def parse_debug_header(headers: Headers) -> bool: return True if headers.get(FLAGS_HEADER, "0") == "1" else False def _parse_parent_id(parts: List[str]) -> OptStr: # parse parent_id part from zipkin single header propagation parent_id = None if len(parts) >= 4: parent_id = parts[3] return parent_id def _parse_debug(parts: List[str]) -> bool: # parse debug part from zipkin single header propagation debug = False if len(parts) >= 3 and parts[2] == DEBUG_MARKER: debug = True return debug def _parse_sampled(parts: List[str]) -> OptBool: # parse sampled part from zipkin single header propagation sampled: OptBool = None if len(parts) >= 3: if parts[2] in ("1", "0"): sampled = bool(int(parts[2])) return sampled def _parse_single_header(headers: Headers) -> Optional[TraceContext]: # Makes TraceContext from zipkin single header format. # https://github.com/openzipkin/b3-propagation # b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} if headers[SINGLE_HEADER] == "0": return None payload = headers[SINGLE_HEADER].lower() parts: List[str] = payload.split(DELIMITER) if len(parts) < 2: return None debug = _parse_debug(parts) sampled = debug if debug else _parse_sampled(parts) context = TraceContext( trace_id=parts[0], span_id=parts[1], parent_id=_parse_parent_id(parts), sampled=sampled, debug=debug, shared=False, ) return context def make_context(headers: Headers) -> Optional[TraceContext]: """Converts available headers to TraceContext, if headers mapping does not contain zipkin headers, function returns None. """ # TODO: add validation for trace_id/span_id/parent_id # normalize header names just in case someone passed regular dict # instead dict with case insensitive keys headers = {k.lower(): v for k, v in headers.items()} required = (TRACE_ID_HEADER.lower(), SPAN_ID_HEADER.lower()) has_b3 = all(h in headers for h in required) has_b3_single = SINGLE_HEADER in headers if not (has_b3_single or has_b3): return None if has_b3: debug = parse_debug_header(headers) sampled = debug if debug else parse_sampled_header(headers) context = TraceContext( trace_id=headers[TRACE_ID_HEADER.lower()], parent_id=headers.get(PARENT_ID_HEADER.lower()), span_id=headers[SPAN_ID_HEADER.lower()], sampled=sampled, debug=debug, shared=False, ) return context return _parse_single_header(headers) OptKeys = Optional[List[str]] def filter_none(data: Dict[str, Any], keys: OptKeys = None) -> Dict[str, Any]: """Filter keys from dict with None values. Check occurs only on root level. If list of keys specified, filter works only for selected keys """ def limited_filter(k: str, v: Any) -> bool: return k not in keys or v is not None # type: ignore def full_filter(k: str, v: Any) -> bool: return v is not None f = limited_filter if keys is not None else full_filter return {k: v for k, v in data.items() if f(k, v)} aiozipkin_1.1.1a1.orig/aiozipkin/log.py0000644000000000000000000000017413174672454015047 0ustar00"""Logging configuration.""" import logging # Name the logger after the package. logger = logging.getLogger(__package__) aiozipkin_1.1.1a1.orig/aiozipkin/mypy_types.py0000644000000000000000000000033713751206174016502 0ustar00import asyncio from typing import Mapping, Optional Headers = Mapping[str, str] OptStr = Optional[str] OptTs = Optional[float] OptInt = Optional[int] OptBool = Optional[bool] OptLoop = Optional[asyncio.AbstractEventLoop] aiozipkin_1.1.1a1.orig/aiozipkin/py.typed0000644000000000000000000000001613327041462015373 0ustar00# placeholder aiozipkin_1.1.1a1.orig/aiozipkin/record.py0000644000000000000000000000515613751206174015542 0ustar00from typing import Any, Dict, List, NamedTuple, Optional, TypeVar from .helpers import CONSUMER, PRODUCER, Endpoint, TraceContext, filter_none from .mypy_types import OptInt, OptStr class Annotation(NamedTuple): value: str timestamp: int def _endpoint_asdict(endpoint: Endpoint) -> Dict[str, Any]: return filter_none(endpoint._asdict()) T = TypeVar("T", bound="Record") class Record: def __init__(self: T, context: TraceContext, local_endpoint: Endpoint) -> None: self._context = context self._local_endpoint = _endpoint_asdict(local_endpoint) self._finished = False self._name = "unknown" self._kind: OptStr = None self._timestamp: OptInt = None self._duration: OptInt = None self._remote_endpoint: Optional[Dict[str, Any]] = None self._annotations: List[Annotation] = [] self._tags: Dict[str, str] = {} @property def context(self) -> TraceContext: return self._context def start(self: T, ts: int) -> T: self._timestamp = ts return self def finish(self: T, ts: OptInt) -> T: if self._finished: return self if self._timestamp is None: raise RuntimeError("Record should be started first") if ts is not None and self._kind not in (PRODUCER, CONSUMER): self._duration = max(ts - self._timestamp, 1) self._finished = True return self def name(self: T, n: str) -> T: self._name = n return self def set_tag(self: T, key: str, value: Any) -> T: self._tags[key] = str(value) return self def annotate(self: T, value: Optional[str], ts: int) -> T: self._annotations.append(Annotation(str(value), int(ts))) return self def kind(self: T, kind: str) -> T: self._kind = kind return self def remote_endpoint(self: T, endpoint: Endpoint) -> T: self._remote_endpoint = _endpoint_asdict(endpoint) return self def asdict(self) -> Dict[str, Any]: c = self._context rec = { "traceId": c.trace_id, "name": self._name, "parentId": c.parent_id, "id": c.span_id, "kind": self._kind, "timestamp": self._timestamp, "duration": self._duration, "debug": c.debug, "shared": c.shared, "localEndpoint": self._local_endpoint, "remoteEndpoint": self._remote_endpoint, "annotations": [a._asdict() for a in self._annotations], "tags": self._tags, } return filter_none(rec, ["kind"]) aiozipkin_1.1.1a1.orig/aiozipkin/sampler.py0000644000000000000000000000135413704354533015724 0ustar00import abc from random import Random from .mypy_types import OptInt class SamplerABC(abc.ABC): @abc.abstractmethod def is_sampled(self, trace_id: str) -> bool: # pragma: no cover """Defines if given trace should be recorded for further actions.""" pass class Sampler(SamplerABC): def __init__(self, *, sample_rate: float = 1.0, seed: OptInt = None) -> None: self._sample_rate = sample_rate self._rng = Random(seed) def is_sampled(self, trace_id: str) -> bool: if self._sample_rate == 0.0: sampled = False else: sampled = self._rng.random() <= self._sample_rate return sampled # TODO: implement other types of sampler for example hash trace_id aiozipkin_1.1.1a1.orig/aiozipkin/span.py0000644000000000000000000001311413751206174015216 0ustar00from abc import ABCMeta, abstractmethod from types import TracebackType from typing import TYPE_CHECKING, List, Optional, Type, TypeVar from .constants import ERROR from .helpers import Endpoint, TraceContext, make_timestamp from .mypy_types import OptInt, OptStr, OptTs from .record import Record if TYPE_CHECKING: from .tracer import Tracer T = TypeVar("T", bound="SpanAbc") class SpanAbc(metaclass=ABCMeta): @property @abstractmethod def is_noop(self: T) -> bool: return True # pragma: no cover @property @abstractmethod def context(self: T) -> TraceContext: pass # pragma: no cover @property @abstractmethod def tracer(self: T) -> "Tracer": pass # pragma: no cover @abstractmethod def start(self: T, ts: OptTs = None) -> T: pass # pragma: no cover @abstractmethod def finish(self: T, ts: OptTs = None, exception: Optional[Exception] = None) -> T: pass # pragma: no cover @abstractmethod def remote_endpoint( self: T, servce_name: OptStr, *, ipv4: OptStr = None, ipv6: OptStr = None, port: OptInt = None ) -> T: pass # pragma: no cover @abstractmethod def tag(self: T, key: str, value: str) -> T: pass # pragma: no cover @abstractmethod def annotate(self: T, value: Optional[str], ts: OptTs = None) -> T: pass # pragma: no cover @abstractmethod def kind(self: T, span_kind: str) -> T: pass # pragma: no cover @abstractmethod def name(self: T, span_name: str) -> T: pass # pragma: no cover @abstractmethod def new_child(self: T, name: OptStr = None, kind: OptStr = None) -> T: pass # pragma: no cover def __enter__(self: T) -> T: self.start() return self def __exit__( self, exc_type: Optional[Type[Exception]], exc_value: Optional[Exception], traceback: Optional[TracebackType], ) -> None: self.finish(exception=exc_value) class NoopSpan(SpanAbc): def __init__( self, tracer: "Tracer", context: TraceContext, ignored_exceptions: Optional[List[Type[Exception]]] = None, ) -> None: self._context = context self._tracer = tracer self._ignored_exceptions = ignored_exceptions or [] @property def is_noop(self) -> bool: return True @property def context(self) -> TraceContext: return self._context @property def tracer(self) -> "Tracer": return self._tracer def start(self, ts: OptTs = None) -> "NoopSpan": return self def finish( self, ts: OptTs = None, exception: Optional[Exception] = None ) -> "NoopSpan": return self def remote_endpoint( self, servce_name: OptStr, *, ipv4: OptStr = None, ipv6: OptStr = None, port: OptInt = None ) -> "NoopSpan": return self def tag(self, key: str, value: str) -> "NoopSpan": return self def annotate(self, value: Optional[str], ts: OptTs = None) -> "NoopSpan": return self def kind(self, span_kind: str) -> "NoopSpan": return self def name(self, span_name: str) -> "NoopSpan": return self def new_child(self, name: OptStr = None, kind: OptStr = None) -> "NoopSpan": context = self._tracer._next_context(self.context) return NoopSpan(self.tracer, context) class Span(SpanAbc): def __init__( self, tracer: "Tracer", context: TraceContext, record: Record, ignored_exceptions: Optional[List[Type[Exception]]] = None, ) -> None: self._context = context self._tracer = tracer self._record = record self._ignored_exceptions = ignored_exceptions or [] @property def is_noop(self) -> bool: return False @property def context(self) -> TraceContext: return self._context @property def tracer(self) -> "Tracer": return self._tracer def start(self, ts: OptTs = None) -> "Span": ts = make_timestamp(ts) self._record.start(ts) return self def finish(self, ts: OptTs = None, exception: Optional[Exception] = None) -> "Span": if exception is not None: if not isinstance(exception, tuple(self._ignored_exceptions)): self.tag(ERROR, str(exception)) ts = make_timestamp(ts) self._record.finish(ts) self._tracer._send(self._record) return self def remote_endpoint( self, servce_name: OptStr, *, ipv4: OptStr = None, ipv6: OptStr = None, port: OptInt = None ) -> "Span": endpoint = Endpoint(servce_name, ipv4, ipv6, port) self._record.remote_endpoint(endpoint) return self def tag(self, key: str, value: str) -> "Span": self._record.set_tag(key, value) return self def annotate(self, value: Optional[str], ts: OptTs = None) -> "Span": ts = make_timestamp(ts) self._record.annotate(value, ts) return self def kind(self, span_kind: str) -> "Span": self._record.kind(span_kind) return self def name(self, span_name: str) -> "Span": self._record.name(span_name) return self def new_child(self, name: OptStr = None, kind: OptStr = None) -> "Span": span = self.tracer.new_child(self.context) if name is not None: span.name(name) if kind is not None: span.kind(kind) return span # type: ignore aiozipkin_1.1.1a1.orig/aiozipkin/tracer.py0000644000000000000000000001113713751206174015540 0ustar00import warnings from typing import ( # noqa TYPE_CHECKING, Any, AsyncContextManager, Awaitable, Dict, List, Optional, Type, ) from .context_managers import _ContextManager from .helpers import Endpoint, TraceContext from .mypy_types import OptBool, OptLoop from .record import Record from .sampler import Sampler, SamplerABC from .span import NoopSpan, Span, SpanAbc from .transport import StubTransport, Transport, TransportABC from .utils import generate_random_64bit_string, generate_random_128bit_string if TYPE_CHECKING: class _Base(AsyncContextManager["Tracer"]): pass else: class _Base(AsyncContextManager): pass class Tracer(_Base): def __init__( self, transport: TransportABC, sampler: SamplerABC, local_endpoint: Endpoint, ignored_exceptions: Optional[List[Type[Exception]]] = None, ) -> None: super().__init__() self._records: Dict[TraceContext, Record] = {} self._transport = transport self._sampler = sampler self._local_endpoint = local_endpoint self._ignored_exceptions = ignored_exceptions or [] def new_trace(self, sampled: OptBool = None, debug: bool = False) -> SpanAbc: context = self._next_context(None, sampled=sampled, debug=debug) return self.to_span(context) def join_span(self, context: TraceContext) -> SpanAbc: new_context = context if context.sampled is None: sampled = self._sampler.is_sampled(context.trace_id) new_context = new_context._replace(sampled=sampled) else: new_context = new_context._replace(shared=True) return self.to_span(new_context) def new_child(self, context: TraceContext) -> SpanAbc: new_context = self._next_context(context) if not context.sampled: return NoopSpan(self, new_context, self._ignored_exceptions) return self.to_span(new_context) def to_span(self, context: TraceContext) -> SpanAbc: if not context.sampled: return NoopSpan(self, context, self._ignored_exceptions) record = Record(context, self._local_endpoint) self._records[context] = record return Span(self, context, record, self._ignored_exceptions) def _send(self, record: Record) -> None: self._records.pop(record.context, None) self._transport.send(record) def _next_context( self, context: Optional[TraceContext] = None, sampled: OptBool = None, debug: bool = False, ) -> TraceContext: span_id = generate_random_64bit_string() if context is not None: new_context = context._replace( span_id=span_id, parent_id=context.span_id, shared=False ) return new_context trace_id = generate_random_128bit_string() if sampled is None: sampled = self._sampler.is_sampled(trace_id) new_context = TraceContext( trace_id=trace_id, parent_id=None, span_id=span_id, sampled=sampled, debug=debug, shared=False, ) return new_context async def close(self) -> None: await self._transport.close() async def __aenter__(self) -> "Tracer": return self async def __aexit__(self, *args: Any) -> None: await self.close() def create( zipkin_address: str, local_endpoint: Endpoint, *, sample_rate: float = 0.01, send_interval: float = 5, loop: OptLoop = None, ignored_exceptions: Optional[List[Type[Exception]]] = None ) -> _ContextManager[Tracer]: if loop is not None: warnings.warn( "loop parameter is deprecated and ignored", DeprecationWarning, stacklevel=2 ) async def build_tracer() -> Tracer: sampler = Sampler(sample_rate=sample_rate) transport = Transport(zipkin_address, send_interval=send_interval) return Tracer(transport, sampler, local_endpoint, ignored_exceptions) result = _ContextManager(build_tracer()) return result def create_custom( local_endpoint: Endpoint, transport: Optional[TransportABC] = None, sampler: Optional[SamplerABC] = None, ignored_exceptions: Optional[List[Type[Exception]]] = None, ) -> _ContextManager[Tracer]: t = transport or StubTransport() sample_rate = 1 # sample everything s = sampler or Sampler(sample_rate=sample_rate) async def build_tracer() -> Tracer: return Tracer(t, s, local_endpoint, ignored_exceptions) result = _ContextManager(build_tracer()) return result aiozipkin_1.1.1a1.orig/aiozipkin/transport.py0000644000000000000000000001314513751206174016315 0ustar00import abc import asyncio import warnings from collections import deque from typing import Any, Awaitable, Callable, Deque, Dict, List, Optional, Tuple import aiohttp from aiohttp.client_exceptions import ClientError from yarl import URL from .log import logger from .mypy_types import OptLoop from .record import Record DEFAULT_TIMEOUT = aiohttp.ClientTimeout(total=5 * 60) BATCHES_MAX_COUNT = 10 ** 4 DataList = List[Dict[str, Any]] SndBatches = Deque[Tuple[int, DataList]] SendDataCoro = Callable[[DataList], Awaitable[bool]] class TransportABC(abc.ABC): @abc.abstractmethod def send(self, record: Record) -> None: # pragma: no cover """Sends data to zipkin collector.""" pass @abc.abstractmethod async def close(self) -> None: # pragma: no cover """Performs additional cleanup actions if required.""" pass class StubTransport(TransportABC): """Dummy transport, which logs spans to a limited queue.""" def __init__(self, queue_length: int = 100) -> None: logger.info("Zipkin address was not provided, using stub transport") self.records: Deque[Record] = deque(maxlen=queue_length) def send(self, record: Record) -> None: self.records.append(record) async def close(self) -> None: pass class BatchManager: def __init__( self, max_size: int, send_interval: float, attempt_count: int, send_data: SendDataCoro, ) -> None: loop = asyncio.get_event_loop() self._max_size = max_size self._send_interval = send_interval self._send_data = send_data self._attempt_count = attempt_count self._max = BATCHES_MAX_COUNT self._sending_batches: SndBatches = deque([], maxlen=self._max) self._active_batch: Optional[DataList] = None self._ender = loop.create_future() self._timer: Optional[asyncio.Future[Any]] = None self._sender_task = asyncio.ensure_future(self._sender_loop()) def add(self, data: Dict[str, Any]) -> None: if self._active_batch is None: self._active_batch = [] self._active_batch.append(data) if len(self._active_batch) >= self._max_size: self._sending_batches.append((0, self._active_batch)) self._active_batch = None if self._timer is not None and not self._timer.done(): self._timer.cancel() async def stop(self) -> None: self._ender.set_result(None) await self._sender_task await self._send() if self._timer is not None: self._timer.cancel() try: await self._timer except asyncio.CancelledError: pass async def _sender_loop(self) -> None: while not self._ender.done(): await self._wait() await self._send() async def _send(self) -> None: if self._active_batch is not None: self._sending_batches.append((0, self._active_batch)) self._active_batch = None batches = self._sending_batches.copy() self._sending_batches = deque([], maxlen=self._max) for attempt, batch in batches: if not await self._send_data(batch): attempt += 1 if attempt < self._attempt_count: self._sending_batches.append((attempt, batch)) async def _wait(self) -> None: self._timer = asyncio.ensure_future(asyncio.sleep(self._send_interval)) await asyncio.wait( [self._timer, self._ender], return_when=asyncio.FIRST_COMPLETED, ) class Transport(TransportABC): def __init__( self, address: str, send_interval: float = 5, loop: OptLoop = None, *, send_max_size: int = 100, send_attempt_count: int = 3, send_timeout: Optional[aiohttp.ClientTimeout] = None ) -> None: if loop is not None: warnings.warn( "loop parameter is deprecated and ignored", DeprecationWarning, stacklevel=2, ) self._address = URL(address) self._queue: DataList = [] self._closing = False self._send_interval = send_interval if send_timeout is None: send_timeout = DEFAULT_TIMEOUT self._session = aiohttp.ClientSession( timeout=send_timeout, headers={"Content-Type": "application/json"}, ) self._batch_manager = BatchManager( send_max_size, send_interval, send_attempt_count, self._send_data, ) def send(self, record: Record) -> None: data = record.asdict() self._batch_manager.add(data) async def _send_data(self, data: DataList) -> bool: try: async with self._session.post(self._address, json=data) as resp: body = await resp.text() if resp.status >= 300: msg = "zipkin responded with code: {} and body: {}".format( resp.status, body ) raise RuntimeError(msg) except (asyncio.TimeoutError, ClientError): return False except Exception as exc: # pylint: disable=broad-except # that code should never fail and break application logger.error("Can not send spans to zipkin", exc_info=exc) return True async def close(self) -> None: if self._closing: return self._closing = True await self._batch_manager.stop() await self._session.close() aiozipkin_1.1.1a1.orig/aiozipkin/utils.py0000644000000000000000000000411414203212452015402 0ustar00import random import struct import time # https://github.com/Yelp/py_zipkin/blob/ # 61f8aa3412f6c1b4e1218ed34cb117e97cc9a6cc/py_zipkin/util.py#L22-L75 def generate_random_64bit_string() -> str: """Returns a 64 bit UTF-8 encoded string. In the interests of simplicity, this is always cast to a `str` instead of (in py2 land) a unicode string. Certain clients (I'm looking at you, Twisted) don't enjoy unicode headers. :returns: random 16-character string """ return f"{random.getrandbits(64):016x}" # https://github.com/Yelp/py_zipkin/blob/ # 61f8aa3412f6c1b4e1218ed34cb117e97cc9a6cc/py_zipkin/util.py#L32 def generate_random_128bit_string() -> str: """Returns a 128 bit UTF-8 encoded string. Follows the same conventions as generate_random_64bit_string(). The upper 32 bits are the current time in epoch seconds, and the lower 96 bits are random. This allows for AWS X-Ray `interop `_ :returns: 32-character hex string """ t = int(time.time()) lower_96 = random.getrandbits(96) return f"{(t << 96) | lower_96:032x}" def unsigned_hex_to_signed_int(hex_string: str) -> int: """Converts a 64-bit hex string to a signed int value. This is due to the fact that Apache Thrift only has signed values. Examples: '17133d482ba4f605' => 1662740067609015813 'b6dbb1c2b362bf51' => -5270423489115668655 :param hex_string: the string representation of a zipkin ID :returns: signed int representation """ v: int = struct.unpack("q", struct.pack("Q", int(hex_string, 16)))[0] return v def signed_int_to_unsigned_hex(signed_int: int) -> str: """Converts a signed int value to a 64-bit hex string. Examples: 1662740067609015813 => '17133d482ba4f605' -5270423489115668655 => 'b6dbb1c2b362bf51' :param signed_int: an int to convert :returns: unsigned hex string """ hex_string = hex(struct.unpack("Q", struct.pack("q", signed_int))[0])[2:] if hex_string.endswith("L"): return hex_string[:-1] return hex_string