Skip to content

Commit

Permalink
Added aiohttp integration
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejmoskala committed May 13, 2021
1 parent a82fa83 commit c439511
Show file tree
Hide file tree
Showing 10 changed files with 755 additions and 0 deletions.
8 changes: 8 additions & 0 deletions contrib/opencensus-ext-aiohttp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## Unreleased

- Added attributes following specs listed [here](https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/HTTP.md#attributes)
([#746](https://github.com/census-instrumentation/opencensus-python/pull/746))
- Support exporter changes in `opencensus>=0.7.0`
- Initial version
53 changes: 53 additions & 0 deletions contrib/opencensus-ext-aiohttp/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
OpenCensus requests Integration
============================================================================

|pypi|

.. |pypi| image:: https://badge.fury.io/py/opencensus-ext-aiohttp.svg
:target: https://pypi.org/project/opencensus-ext-aiohttp/

OpenCensus can trace asynchronous HTTP requests made with the `aiohttp package`_. The request URL,
method, and status will be collected.

You can enable aiohttp integration by specifying ``'aiohttp'`` to ``trace_integrations``.

It's possible to configure a list of URL you don't want traced, anf it's configurable by giving an array of
hostname/port to the attribute ``excludelist_hostnames`` in OpenCensus context's attributes:


.. _aiohttp package: https://pypi.python.org/pypi/aiohttp

Installation
------------

::

pip install opencensus-ext-requests

Usage
-----

.. code:: python
import asyncio
from aiohttp import ClientSession
from opencensus.trace import config_integration
from opencensus.trace.tracer import Tracer
config_integration.trace_integrations(['aiohttp'])
async def main():
tracer = Tracer()
with tracer.span(name='parent'):
client_session = ClientSession()
async with client_session.get(url='https://www.wikipedia.org/wiki/Rabbit') as response:
await response.read()
asyncio.run(main())
References
----------

* `OpenCensus Project <https://opencensus.io/>`_
1 change: 1 addition & 0 deletions contrib/opencensus-ext-aiohttp/opencensus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
1 change: 1 addition & 0 deletions contrib/opencensus-ext-aiohttp/opencensus/ext/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
17 changes: 17 additions & 0 deletions contrib/opencensus-ext-aiohttp/opencensus/ext/aiohttp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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.

from opencensus.ext.aiohttp import trace

__all__ = ["trace"]
131 changes: 131 additions & 0 deletions contrib/opencensus-ext-aiohttp/opencensus/ext/aiohttp/trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# 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 asyncio
import logging

import wrapt
from aiohttp import InvalidURL, ServerTimeoutError
from yarl import URL

from opencensus.trace import (
attributes_helper,
exceptions_status,
execution_context,
utils,
)
from opencensus.trace.span import SpanKind

logger = logging.getLogger(__name__)

MODULE_NAME = "aiohttp"

COMPONENT = attributes_helper.COMMON_ATTRIBUTES["COMPONENT"]
HTTP_COMPONENT = "HTTP"
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 aiohttp library to trace it."""
logger.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)

# Wrap Session class
wrapt.wrap_function_wrapper(
module=MODULE_NAME, name="ClientSession._request", wrapper=wrap_session_request
)


async def wrap_session_request(wrapped, _, args, kwargs):
"""Wrap the session function to trace it."""
if execution_context.is_exporter():
return await wrapped(*args, **kwargs)

method = kwargs.get("method") or args[0]
str_or_url = kwargs.get("str_or_url") or args[1]
try:
url = URL(str_or_url)
except ValueError as e:
raise InvalidURL(str_or_url) from e

excludelist_hostnames = execution_context.get_opencensus_attr(
"excludelist_hostnames"
)
url_host_with_port = url.host + (f":{url.port}" if url.port else "")
if utils.disable_tracing_hostname(url_host_with_port, excludelist_hostnames):
return await wrapped(*args, **kwargs)

url_path = url.path or "/"

tracer = execution_context.get_opencensus_tracer()
with tracer.span(name=url_path) as span:
span.span_kind = SpanKind.CLIENT

try:
tracer_headers = tracer.propagator.to_headers(
span_context=tracer.span_context,
)
kwargs.setdefault("headers", {}).update(tracer_headers)
except Exception:
pass

span.add_attribute(
attribute_key=COMPONENT,
attribute_value=HTTP_COMPONENT,
)
span.add_attribute(
attribute_key=HTTP_HOST,
attribute_value=url_host_with_port,
)
span.add_attribute(
attribute_key=HTTP_METHOD,
attribute_value=method.upper(),
)
span.add_attribute(
attribute_key=HTTP_PATH,
attribute_value=url_path,
)
span.add_attribute(
attribute_key=HTTP_URL,
attribute_value=str(url),
)

try:
result = await wrapped(*args, **kwargs)
except (ServerTimeoutError, asyncio.TimeoutError):
span.set_status(exceptions_status.TIMEOUT)
raise
except InvalidURL:
span.set_status(exceptions_status.INVALID_URL)
raise
except Exception as e:
span.set_status(exceptions_status.unknown(e))
raise
else:
status_code = int(result.status)
span.add_attribute(
attribute_key=HTTP_STATUS_CODE,
attribute_value=status_code,
)
span.set_status(utils.status_from_http_code(http_code=status_code))
return result
2 changes: 2 additions & 0 deletions contrib/opencensus-ext-aiohttp/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1
48 changes: 48 additions & 0 deletions contrib/opencensus-ext-aiohttp/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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-aiohttp",
version=__version__, # noqa
author="OpenCensus Authors",
author_email="[email protected]",
classifiers=[
"Intended Audience :: Developers",
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
],
description="OpenCensus Requests Integration",
include_package_data=True,
long_description=open("README.rst").read(),
install_requires=[
"opencensus >= 0.8.dev0, < 1.0.0",
"aiohttp >= 3.7.4, < 4.0.0",
],
extras_require={},
license="Apache-2.0",
packages=find_packages(exclude=("tests",)),
namespace_packages=[],
url="https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-aiohttp", # noqa: E501
zip_safe=False,
)
Loading

0 comments on commit c439511

Please sign in to comment.