From 7f8f75e2d435dad5979ae2f7d1a858a1b6308a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Klich?= Date: Tue, 8 Feb 2022 22:14:55 +0100 Subject: [PATCH] Add httpx tracing ext --- README.rst | 2 + contrib/opencensus-ext-httpx/README.rst | 42 ++ .../opencensus/__init__.py | 0 .../opencensus/ext/__init__.py | 0 .../opencensus/ext/httpx/__init__.py | 0 .../opencensus/ext/httpx/trace.py | 114 +++++ contrib/opencensus-ext-httpx/setup.cfg | 2 + contrib/opencensus-ext-httpx/setup.py | 49 ++ .../tests/test_httpx_trace.py | 460 ++++++++++++++++++ contrib/opencensus-ext-httpx/version.py | 1 + noxfile.py | 1 + opencensus/trace/integrations.py | 1 + tox.ini | 1 + 13 files changed, 673 insertions(+) create mode 100644 contrib/opencensus-ext-httpx/README.rst create mode 100644 contrib/opencensus-ext-httpx/opencensus/__init__.py create mode 100644 contrib/opencensus-ext-httpx/opencensus/ext/__init__.py create mode 100644 contrib/opencensus-ext-httpx/opencensus/ext/httpx/__init__.py create mode 100644 contrib/opencensus-ext-httpx/opencensus/ext/httpx/trace.py create mode 100644 contrib/opencensus-ext-httpx/setup.cfg create mode 100644 contrib/opencensus-ext-httpx/setup.py create mode 100644 contrib/opencensus-ext-httpx/tests/test_httpx_trace.py create mode 100644 contrib/opencensus-ext-httpx/version.py diff --git a/README.rst b/README.rst index 4bc43845d..5edf1ed77 100644 --- a/README.rst +++ b/README.rst @@ -199,6 +199,7 @@ OpenCensus supports integration with popular web frameworks, client libraries an - `Google Cloud Client Libraries`_ - `gRPC`_ - `httplib`_ +- `httpx`_ - `logging`_ - `MySQL`_ - `PostgreSQL`_ @@ -244,6 +245,7 @@ Trace Exporter .. _Google Cloud Client Libraries: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-google-cloud-clientlibs .. _gRPC: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-grpc .. _httplib: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-httplib +.. _httpx: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-httpx .. _Jaeger: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-jaeger .. _logging: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-logging .. _MySQL: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-mysql diff --git a/contrib/opencensus-ext-httpx/README.rst b/contrib/opencensus-ext-httpx/README.rst new file mode 100644 index 000000000..6e4d5b87a --- /dev/null +++ b/contrib/opencensus-ext-httpx/README.rst @@ -0,0 +1,42 @@ +OpenCensus httpx Integration +============================================================================ + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opencensus-ext-httpx.svg + :target: https://pypi.org/project/opencensus-ext-httpx/ + +OpenCensus can trace HTTP requests made with the `httpx package `_. The request URL, +method, and status will be collected. + +You can enable httpx integration by specifying ``'httpx'`` to ``trace_integrations``. + +Only the hostname must be specified if only the hostname is specified in the URL request. + + +Installation +------------ + +:: + + pip install opencensus-ext-httpx + +Usage +----- + +.. code:: python + + import httpx + from opencensus.trace import config_integration + from opencensus.trace.tracer import Tracer + + if __name__ == '__main__': + config_integration.trace_integrations(['httpx']) + tracer = Tracer() + with tracer.span(name='parent'): + response = httpx.get(url='https://www.example.org') + +References +---------- + +* `OpenCensus Project `_ diff --git a/contrib/opencensus-ext-httpx/opencensus/__init__.py b/contrib/opencensus-ext-httpx/opencensus/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/opencensus-ext-httpx/opencensus/ext/__init__.py b/contrib/opencensus-ext-httpx/opencensus/ext/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/opencensus-ext-httpx/opencensus/ext/httpx/__init__.py b/contrib/opencensus-ext-httpx/opencensus/ext/httpx/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/opencensus-ext-httpx/opencensus/ext/httpx/trace.py b/contrib/opencensus-ext-httpx/opencensus/ext/httpx/trace.py new file mode 100644 index 000000000..bc288d66a --- /dev/null +++ b/contrib/opencensus-ext-httpx/opencensus/ext/httpx/trace.py @@ -0,0 +1,114 @@ +import logging + +import httpx +import wrapt + +from opencensus.trace import ( + attributes_helper, + exceptions_status, + execution_context, + integrations, +) +from opencensus.trace import span as span_module +from opencensus.trace import utils + +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + + +log = logging.getLogger(__name__) + +MODULE_NAME = "httpx" + +HTTP_HOST = attributes_helper.COMMON_ATTRIBUTES["HTTP_HOST"] +HTTP_METHOD = attributes_helper.COMMON_ATTRIBUTES["HTTP_METHOD"] +HTTP_PATH = attributes_helper.COMMON_ATTRIBUTES["HTTP_PATH"] +HTTP_ROUTE = attributes_helper.COMMON_ATTRIBUTES["HTTP_ROUTE"] +HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES["HTTP_STATUS_CODE"] +HTTP_URL = attributes_helper.COMMON_ATTRIBUTES["HTTP_URL"] + + +def trace_integration(tracer=None): + """Wrap the requests library to trace it.""" + log.info("Integrated module: {}".format(MODULE_NAME)) + + if tracer is not None: + # The execution_context tracer should never be None - if it has not + # been set it returns a no-op tracer. Most code in this library does + # not handle None being used in the execution context. + execution_context.set_opencensus_tracer(tracer) + + wrapt.wrap_function_wrapper(MODULE_NAME, "Client.request", wrap_client_request) + # pylint: disable=protected-access + integrations.add_integration(integrations._Integrations.HTTPX) + + +def wrap_client_request(wrapped, instance, args, kwargs): + """Wrap the session function to trace it.""" + # Check if request was sent from an exporter. If so, do not wrap. + if execution_context.is_exporter(): + return wrapped(*args, **kwargs) + + method = kwargs.get("method") or args[0] + url = kwargs.get("url") or args[1] + + excludelist_hostnames = execution_context.get_opencensus_attr( + "excludelist_hostnames" + ) + parsed_url = urlparse(url) + if parsed_url.port is None: + dest_url = parsed_url.hostname + else: + dest_url = "{}:{}".format(parsed_url.hostname, parsed_url.port) + if utils.disable_tracing_hostname(dest_url, excludelist_hostnames): + return wrapped(*args, **kwargs) + + path = parsed_url.path if parsed_url.path else "/" + + _tracer = execution_context.get_opencensus_tracer() + _span = _tracer.start_span() + + _span.name = "{}".format(path) + _span.span_kind = span_module.SpanKind.CLIENT + + try: + tracer_headers = _tracer.propagator.to_headers(_tracer.span_context) + kwargs.setdefault("headers", {}).update(tracer_headers) + except Exception: # pragma: NO COVER + pass + + # Add the component type to attributes + _tracer.add_attribute_to_current_span("component", "HTTP") + + # Add the requests host to attributes + _tracer.add_attribute_to_current_span(HTTP_HOST, dest_url) + + # Add the requests method to attributes + _tracer.add_attribute_to_current_span(HTTP_METHOD, method.upper()) + + # Add the requests path to attributes + _tracer.add_attribute_to_current_span(HTTP_PATH, path) + + # Add the requests url to attributes + _tracer.add_attribute_to_current_span(HTTP_URL, url) + + try: + result = wrapped(*args, **kwargs) + except httpx.TimeoutException: + _span.set_status(exceptions_status.TIMEOUT) + raise + except httpx.InvalidURL: + _span.set_status(exceptions_status.INVALID_URL) + raise + except Exception as e: + _span.set_status(exceptions_status.unknown(e)) + raise + else: + # Add the status code to attributes + _tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, result.status_code) + _span.set_status(utils.status_from_http_code(result.status_code)) + return result + finally: + _tracer.end_span() diff --git a/contrib/opencensus-ext-httpx/setup.cfg b/contrib/opencensus-ext-httpx/setup.cfg new file mode 100644 index 000000000..2a9acf13d --- /dev/null +++ b/contrib/opencensus-ext-httpx/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/contrib/opencensus-ext-httpx/setup.py b/contrib/opencensus-ext-httpx/setup.py new file mode 100644 index 000000000..c2790eddd --- /dev/null +++ b/contrib/opencensus-ext-httpx/setup.py @@ -0,0 +1,49 @@ +# Copyright 2019, OpenCensus 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 setuptools import find_packages, setup +from version import __version__ + +setup( + name="opencensus-ext-httpx", + version=__version__, # noqa + author="MichaƂ Klich", + author_email="michal@klichx.dev", + classifiers=[ + "Intended Audience :: Developers", + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + ], + description="OpenCensus HTTPX Integration", + include_package_data=True, + long_description="", + install_requires=["opencensus >= 0.12.dev0, < 1.0.0", "httpx >= 0.22.0"], + extras_require={}, + license="Apache-2.0", + packages=find_packages(exclude=("tests",)), + namespace_packages=[], + url="", + zip_safe=False, +) diff --git a/contrib/opencensus-ext-httpx/tests/test_httpx_trace.py b/contrib/opencensus-ext-httpx/tests/test_httpx_trace.py new file mode 100644 index 000000000..dbfaf0b9e --- /dev/null +++ b/contrib/opencensus-ext-httpx/tests/test_httpx_trace.py @@ -0,0 +1,460 @@ +# Copyright 2017, OpenCensus Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import httpx +import mock + +from opencensus.ext.httpx import trace +from opencensus.trace import execution_context +from opencensus.trace import span as span_module +from opencensus.trace import status as status_module +from opencensus.trace.tracers import noop_tracer + + +class Test_httpx_trace(unittest.TestCase): + def test_trace_integration(self): + mock_wrap = mock.Mock() + patch_wrapt = mock.patch('wrapt.wrap_function_wrapper', mock_wrap) + + with patch_wrapt: + trace.trace_integration() + + self.assertIsInstance(execution_context.get_opencensus_tracer(), + noop_tracer.NoopTracer) + mock_wrap.assert_called_once_with( + trace.MODULE_NAME, + 'Client.request', + trace.wrap_client_request) + + def test_trace_integration_set_tracer(self): + mock_wrap = mock.Mock() + patch_wrapt = mock.patch('wrapt.wrap_function_wrapper', mock_wrap) + + class TmpTracer(noop_tracer.NoopTracer): + pass + + with patch_wrapt: + trace.trace_integration(tracer=TmpTracer()) + + self.assertIsInstance(execution_context.get_opencensus_tracer(), + TmpTracer) + mock_wrap.assert_called_once_with( + trace.MODULE_NAME, + 'Client.request', + trace.wrap_client_request) + + def test_wrap_client_request(self): + wrapped = mock.Mock(return_value=mock.Mock(status_code=200)) + + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080/test' + request_method = 'POST' + kwargs = {} + + with patch, patch_thread: + trace.wrap_client_request( + wrapped, 'Client.request', + (request_method, url), kwargs + ) + + expected_attributes = { + 'component': 'HTTP', + 'http.host': 'localhost:8080', + 'http.method': 'POST', + 'http.path': '/test', + 'http.status_code': 200, + 'http.url': url, + } + expected_name = '/test' + expected_status = status_module.Status(0) + + self.assertEqual(span_module.SpanKind.CLIENT, + mock_tracer.current_span.span_kind) + self.assertEqual(expected_attributes, + mock_tracer.current_span.attributes) + self.assertEqual(kwargs['headers']['x-trace'], 'some-value') + self.assertEqual(expected_name, mock_tracer.current_span.name) + self.assertEqual( + expected_status.__dict__, + mock_tracer.current_span.status.__dict__ + ) + + def test_wrap_client_request_excludelist_ok(self): + def wrapped(*args, **kwargs): + result = mock.Mock() + result.status_code = 200 + return result + + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch_tracer = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_attr = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_attr', + return_value=None) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost/' + request_method = 'POST' + + with patch_tracer, patch_attr, patch_thread: + trace.wrap_client_request( + wrapped, 'Client.request', + (request_method, url), {} + ) + + expected_name = '/' + self.assertEqual(expected_name, mock_tracer.current_span.name) + + def test_wrap_client_request_excludelist_nok(self): + def wrapped(*args, **kwargs): + result = mock.Mock() + result.status_code = 200 + return result + + mock_tracer = MockTracer() + + patch_tracer = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_attr = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_attr', + return_value=['localhost:8080']) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080' + request_method = 'POST' + + with patch_tracer, patch_attr, patch_thread: + trace.wrap_client_request( + wrapped, 'Client.request', + (request_method, url), {} + ) + + self.assertEqual(None, mock_tracer.current_span) + + def test_wrap_client_request_exporter_thread(self): + def wrapped(*args, **kwargs): + result = mock.Mock() + result.status_code = 200 + return result + + mock_tracer = MockTracer() + + patch_tracer = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_attr = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_attr', + return_value=['localhost:8080']) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=True) + + url = 'http://localhost:8080' + request_method = 'POST' + + with patch_tracer, patch_attr, patch_thread: + trace.wrap_client_request( + wrapped, 'Session.request', + (request_method, url), {} + ) + + self.assertEqual(None, mock_tracer.current_span) + + def test_header_is_passed_in(self): + wrapped = mock.Mock(return_value=mock.Mock(status_code=200)) + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080' + request_method = 'POST' + kwargs = {} + + with patch, patch_thread: + trace.wrap_client_request( + wrapped, 'Session.request', + (request_method, url), kwargs + ) + + self.assertEqual(kwargs['headers']['x-trace'], 'some-value') + + def test_headers_are_preserved(self): + wrapped = mock.Mock(return_value=mock.Mock(status_code=200)) + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080' + request_method = 'POST' + kwargs = {'headers': {'key': 'value'}} + + with patch, patch_thread: + trace.wrap_client_request( + wrapped, 'Session.request', + (request_method, url), kwargs + ) + + self.assertEqual(kwargs['headers']['key'], 'value') + self.assertEqual(kwargs['headers']['x-trace'], 'some-value') + + def test_tracer_headers_are_overwritten(self): + wrapped = mock.Mock(return_value=mock.Mock(status_code=200)) + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080' + request_method = 'POST' + kwargs = {'headers': {'x-trace': 'original-value'}} + + with patch, patch_thread: + trace.wrap_client_request( + wrapped, 'Session.request', + (request_method, url), kwargs + ) + + self.assertEqual(kwargs['headers']['x-trace'], 'some-value') + + def test_wrap_client_request_timeout(self): + wrapped = mock.Mock(return_value=mock.Mock(status_code=200)) + wrapped.side_effect = httpx.TimeoutException + + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080/test' + request_method = 'POST' + kwargs = {} + + with patch, patch_thread: + with self.assertRaises(httpx.TimeoutException): + trace.wrap_client_request( + wrapped, 'Session.request', + (request_method, url), kwargs + ) + + expected_attributes = { + 'component': 'HTTP', + 'http.host': 'localhost:8080', + 'http.method': 'POST', + 'http.path': '/test', + 'http.url': url, + } + expected_name = '/test' + expected_status = status_module.Status(4, 'request timed out') + + self.assertEqual(span_module.SpanKind.CLIENT, + mock_tracer.current_span.span_kind) + self.assertEqual(expected_attributes, + mock_tracer.current_span.attributes) + self.assertEqual(kwargs['headers']['x-trace'], 'some-value') + self.assertEqual(expected_name, mock_tracer.current_span.name) + self.assertEqual( + expected_status.__dict__, + mock_tracer.current_span.status.__dict__ + ) + + def test_wrap_client_request_invalid_url(self): + wrapped = mock.Mock(return_value=mock.Mock(status_code=200)) + wrapped.side_effect = httpx.URLRequired + + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080/test' + request_method = 'POST' + kwargs = {} + + with patch, patch_thread: + with self.assertRaises(httpx.URLRequired): + trace.wrap_client_request( + wrapped, 'Session.request', + (request_method, url), kwargs + ) + + expected_attributes = { + 'component': 'HTTP', + 'http.host': 'localhost:8080', + 'http.method': 'POST', + 'http.path': '/test', + 'http.url': url, + } + expected_name = '/test' + expected_status = status_module.Status(3, 'invalid URL') + + self.assertEqual(span_module.SpanKind.CLIENT, + mock_tracer.current_span.span_kind) + self.assertEqual(expected_attributes, + mock_tracer.current_span.attributes) + self.assertEqual(kwargs['headers']['x-trace'], 'some-value') + self.assertEqual(expected_name, mock_tracer.current_span.name) + self.assertEqual( + expected_status.__dict__, + mock_tracer.current_span.status.__dict__ + ) + + def test_wrap_client_request_exception(self): + wrapped = mock.Mock(return_value=mock.Mock(status_code=200)) + wrapped.side_effect = httpx.TooManyRedirects + + mock_tracer = MockTracer( + propagator=mock.Mock( + to_headers=lambda x: {'x-trace': 'some-value'})) + + patch = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'get_opencensus_tracer', + return_value=mock_tracer) + patch_thread = mock.patch( + 'opencensus.ext.httpx.trace.execution_context.' + 'is_exporter', + return_value=False) + + url = 'http://localhost:8080/test' + request_method = 'POST' + kwargs = {} + + with patch, patch_thread: + with self.assertRaises(httpx.TooManyRedirects): + trace.wrap_client_request( + wrapped, 'Session.request', + (request_method, url), kwargs + ) + + expected_attributes = { + 'component': 'HTTP', + 'http.host': 'localhost:8080', + 'http.method': 'POST', + 'http.path': '/test', + 'http.url': url, + } + expected_name = '/test' + expected_status = status_module.Status(2, '') + + self.assertEqual(span_module.SpanKind.CLIENT, + mock_tracer.current_span.span_kind) + self.assertEqual(expected_attributes, + mock_tracer.current_span.attributes) + self.assertEqual(kwargs['headers']['x-trace'], 'some-value') + self.assertEqual(expected_name, mock_tracer.current_span.name) + self.assertEqual( + expected_status.__dict__, + mock_tracer.current_span.status.__dict__ + ) + + +class MockTracer(object): + def __init__(self, propagator=None): + self.current_span = None + self.span_context = {} + self.propagator = propagator + + def start_span(self): + span = MockSpan() + self.current_span = span + return span + + def end_span(self): + pass + + def add_attribute_to_current_span(self, key, value): + self.current_span.attributes[key] = value + + +class MockSpan(object): + def __init__(self): + self.attributes = {} + + def set_status(self, status): + self.status = status diff --git a/contrib/opencensus-ext-httpx/version.py b/contrib/opencensus-ext-httpx/version.py new file mode 100644 index 000000000..b794fd409 --- /dev/null +++ b/contrib/opencensus-ext-httpx/version.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/noxfile.py b/noxfile.py index abf5aa8c4..466079fc8 100644 --- a/noxfile.py +++ b/noxfile.py @@ -32,6 +32,7 @@ def _install_dev_packages(session): session.install('-e', 'contrib/opencensus-ext-gevent') session.install('-e', 'contrib/opencensus-ext-grpc') session.install('-e', 'contrib/opencensus-ext-httplib') + session.install('-e', 'contrib/opencensus-ext-httpx') session.install('-e', 'contrib/opencensus-ext-jaeger') session.install('-e', 'contrib/opencensus-ext-logging') session.install('-e', 'contrib/opencensus-ext-mysql') diff --git a/opencensus/trace/integrations.py b/opencensus/trace/integrations.py index e8e73f1bf..c5b09a850 100644 --- a/opencensus/trace/integrations.py +++ b/opencensus/trace/integrations.py @@ -32,6 +32,7 @@ class _Integrations: PYRAMID = 512 REQUESTS = 1024 SQLALCHEMY = 2056 + HTTPX = 4096 def get_integrations(): diff --git a/tox.ini b/tox.ini index d22edbfd9..25990741b 100644 --- a/tox.ini +++ b/tox.ini @@ -45,6 +45,7 @@ deps = unit,lint,bandit: -e contrib/opencensus-ext-pymysql unit,lint,bandit: -e contrib/opencensus-ext-pyramid unit,lint,bandit: -e contrib/opencensus-ext-requests + unit,lint,bandit: -e contrib/opencensus-ext-httpx unit,lint,bandit: -e contrib/opencensus-ext-sqlalchemy py3{6,7,8,9}-unit,lint,bandit: -e contrib/opencensus-ext-stackdriver unit,lint,bandit: -e contrib/opencensus-ext-threading