Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 3b3df7c

Browse files
committed
Added aiohttp integration
1 parent a82fa83 commit 3b3df7c

File tree

10 files changed

+755
-0
lines changed

10 files changed

+755
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
- Added attributes following specs listed [here](https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/HTTP.md#attributes)
6+
([#746](https://github.com/census-instrumentation/opencensus-python/pull/746))
7+
- Support exporter changes in `opencensus>=0.7.0`
8+
- Initial version
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
OpenCensus requests Integration
2+
============================================================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opencensus-ext-aiohttp.svg
7+
:target: https://pypi.org/project/opencensus-ext-aiohttp/
8+
9+
OpenCensus can trace asynchronous HTTP requests made with the `aiohttp package`_. The request URL,
10+
method, and status will be collected.
11+
12+
You can enable aiohttp integration by specifying ``'aiohttp'`` to ``trace_integrations``.
13+
14+
It's possible to configure a list of URL you don't want traced, anf it's configurable by giving an array of
15+
hostname/port to the attribute ``excludelist_hostnames`` in OpenCensus context's attributes:
16+
17+
18+
.. _aiohttp package: https://pypi.python.org/pypi/aiohttp
19+
20+
Installation
21+
------------
22+
23+
::
24+
25+
pip install opencensus-ext-requests
26+
27+
Usage
28+
-----
29+
30+
.. code:: python
31+
32+
import asyncio
33+
from aiohttp import ClientSession
34+
from opencensus.trace import config_integration
35+
from opencensus.trace.tracer import Tracer
36+
37+
38+
config_integration.trace_integrations(['aiohttp'])
39+
40+
async def main():
41+
tracer = Tracer()
42+
with tracer.span(name='parent'):
43+
client_session = ClientSession()
44+
async with client_session.get(url='https://www.wikipedia.org/wiki/Rabbit') as response:
45+
await response.read()
46+
47+
48+
asyncio.run(main())
49+
50+
References
51+
----------
52+
53+
* `OpenCensus Project <https://opencensus.io/>`_
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opencensus.ext.aiohttp import trace
16+
17+
__all__ = ["trace"]
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import asyncio
15+
import logging
16+
17+
import wrapt
18+
from aiohttp import InvalidURL, ServerTimeoutError
19+
from yarl import URL
20+
21+
from opencensus.trace import (
22+
attributes_helper,
23+
exceptions_status,
24+
execution_context,
25+
utils,
26+
)
27+
from opencensus.trace.span import SpanKind
28+
29+
logger = logging.getLogger(__name__)
30+
31+
MODULE_NAME = "aiohttp"
32+
33+
COMPONENT = attributes_helper.COMMON_ATTRIBUTES["COMPONENT"]
34+
HTTP_COMPONENT = "HTTP"
35+
HTTP_HOST = attributes_helper.COMMON_ATTRIBUTES["HTTP_HOST"]
36+
HTTP_METHOD = attributes_helper.COMMON_ATTRIBUTES["HTTP_METHOD"]
37+
HTTP_PATH = attributes_helper.COMMON_ATTRIBUTES["HTTP_PATH"]
38+
HTTP_ROUTE = attributes_helper.COMMON_ATTRIBUTES["HTTP_ROUTE"]
39+
HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES["HTTP_STATUS_CODE"]
40+
HTTP_URL = attributes_helper.COMMON_ATTRIBUTES["HTTP_URL"]
41+
42+
43+
def trace_integration(tracer=None):
44+
"""Wrap the aiohttp library to trace it."""
45+
logger.info("Integrated module: {}".format(MODULE_NAME))
46+
47+
if tracer is not None:
48+
# The execution_context tracer should never be None - if it has not
49+
# been set it returns a no-op tracer. Most code in this library does
50+
# not handle None being used in the execution context.
51+
execution_context.set_opencensus_tracer(tracer)
52+
53+
# Wrap Session class
54+
wrapt.wrap_function_wrapper(
55+
module=MODULE_NAME, name="ClientSession._request", wrapper=wrap_session_request
56+
)
57+
58+
59+
async def wrap_session_request(wrapped, _, args, kwargs):
60+
"""Wrap the session function to trace it."""
61+
if execution_context.is_exporter():
62+
return await wrapped(*args, **kwargs)
63+
64+
method = kwargs.get("method") or args[0]
65+
str_or_url = kwargs.get("str_or_url") or args[1]
66+
try:
67+
url = URL(str_or_url)
68+
except ValueError as e:
69+
raise InvalidURL(str_or_url) from e
70+
71+
excludelist_hostnames = execution_context.get_opencensus_attr(
72+
"excludelist_hostnames"
73+
)
74+
url_host_with_port = url.host + (f":{url.port}" if url.port else "")
75+
if utils.disable_tracing_hostname(url_host_with_port, excludelist_hostnames):
76+
return await wrapped(*args, **kwargs)
77+
78+
url_path = url.path or "/"
79+
80+
tracer = execution_context.get_opencensus_tracer()
81+
with tracer.span(name=url_path) as span:
82+
span.span_kind = SpanKind.CLIENT
83+
84+
try:
85+
tracer_headers = tracer.propagator.to_headers(
86+
span_context=tracer.span_context,
87+
)
88+
kwargs.setdefault("headers", {}).update(tracer_headers)
89+
except Exception:
90+
pass
91+
92+
span.add_attribute(
93+
attribute_key=COMPONENT,
94+
attribute_value=HTTP_COMPONENT,
95+
)
96+
span.add_attribute(
97+
attribute_key=HTTP_HOST,
98+
attribute_value=url_host_with_port,
99+
)
100+
span.add_attribute(
101+
attribute_key=HTTP_METHOD,
102+
attribute_value=method.upper(),
103+
)
104+
span.add_attribute(
105+
attribute_key=HTTP_PATH,
106+
attribute_value=url_path,
107+
)
108+
span.add_attribute(
109+
attribute_key=HTTP_URL,
110+
attribute_value=str(url),
111+
)
112+
113+
try:
114+
result = await wrapped(*args, **kwargs)
115+
except (ServerTimeoutError, asyncio.TimeoutError):
116+
span.set_status(exceptions_status.TIMEOUT)
117+
raise
118+
except InvalidURL:
119+
span.set_status(exceptions_status.INVALID_URL)
120+
raise
121+
except Exception as e:
122+
span.set_status(exceptions_status.unknown(e))
123+
raise
124+
else:
125+
status_code = int(result.status)
126+
span.add_attribute(
127+
attribute_key=HTTP_STATUS_CODE,
128+
attribute_value=status_code,
129+
)
130+
span.set_status(utils.status_from_http_code(http_code=status_code))
131+
return result
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[bdist_wheel]
2+
universal = 1
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from setuptools import find_packages, setup
16+
from version import __version__
17+
18+
setup(
19+
name="opencensus-ext-aiohttp",
20+
version=__version__, # noqa
21+
author="OpenCensus Authors",
22+
author_email="[email protected]",
23+
classifiers=[
24+
"Intended Audience :: Developers",
25+
"Development Status :: 3 - Alpha",
26+
"Intended Audience :: Developers",
27+
"License :: OSI Approved :: Apache Software License",
28+
"Programming Language :: Python",
29+
"Programming Language :: Python :: 3",
30+
"Programming Language :: Python :: 3.6",
31+
"Programming Language :: Python :: 3.7",
32+
"Programming Language :: Python :: 3.8",
33+
"Programming Language :: Python :: 3.9",
34+
],
35+
description="OpenCensus Requests Integration",
36+
include_package_data=True,
37+
long_description=open("README.rst").read(),
38+
install_requires=[
39+
"opencensus >= 0.8.dev0, < 1.0.0",
40+
"aiohttp >= 3.7.4, < 4.0.0",
41+
],
42+
extras_require={},
43+
license="Apache-2.0",
44+
packages=find_packages(exclude=("tests",)),
45+
namespace_packages=[],
46+
url="https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-aiohttp", # noqa: E501
47+
zip_safe=False,
48+
)

0 commit comments

Comments
 (0)