Skip to content

Commit a3e603e

Browse files
authored
Add integration for aiohttp (#13)
Add integration for aiohttp
1 parent a573656 commit a3e603e

File tree

16 files changed

+1836
-350
lines changed

16 files changed

+1836
-350
lines changed

.github/workflows/ci.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,15 @@ jobs:
8686
run: echo "PYTHONPATH=$PWD" >> $GITHUB_ENV
8787

8888
- name: Run tests
89-
run: pdm run pytest tests --cov=asgi_monitor --junitxml=junitxml.xml --cov-report "xml:coverage.xml"; exit ${PIPESTATUS[0]}
89+
run: pdm run pytest tests --cov=asgi_monitor --junitxml=junitxml.xml --cov-report term --cov-report "xml:coverage.xml"; exit ${PIPESTATUS[0]}
9090

9191
- name: Test coverage comment
9292
uses: MishaKav/pytest-coverage-comment@main
93+
if: github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') || github.event_name == 'pull_request' && github.base_ref == 'master'
9394
with:
9495
pytest-xml-coverage-path: ./coverage.xml
9596
junitxml-path: ./junitxml.xml
9697
junitxml-title: ${{ matrix.python-version }}
9798
unique-id-for-comment: ${{ matrix.python-version }}
9899
report-only-changed-files: true
100+
create-new-comment: true

CONTRIBUTING.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Development
2+
3+
After cloning the project, you'll need to set up the development environment. Here are the guidelines on how to do this.
4+
5+
The development workflow should look like this:
6+
- Add feature/fix bug
7+
- Run tests
8+
- Add tests
9+
- Run tests
10+
- Lint
11+
- Add docs
12+
- Open PR
13+
14+
## Create venv with [PDM](https://pdm-project.org/en/latest/)
15+
16+
```bash
17+
pdm venv create 3.10
18+
```
19+
20+
## Activate venv
21+
22+
```bash
23+
source .venv/bin/activate
24+
```
25+
26+
## Install dependencies with [just](https://github.com/casey/just)
27+
28+
```bash
29+
just install
30+
```
31+
or
32+
```bash
33+
pdm install -G:all
34+
pip install -r docs/requirements.txt
35+
pre-commit install
36+
```
37+
38+
## Running tests
39+
40+
```bash
41+
just test
42+
```
43+
or
44+
```bash
45+
pytest tests --cov=asgi_monitor --cov-append --cov-report term-missing -v
46+
```
47+
48+
## Running lint
49+
50+
```bash
51+
just lint
52+
```
53+
or
54+
```bash
55+
pre-commit run --all-files
56+
```
57+
58+
## Build documentation
59+
60+
```bash
61+
just doc
62+
```
63+
or
64+
```bash
65+
sphinx-build -M html docs docs-build
66+
echo "Open file://`pwd`/docs-build/html/index.html"
67+
```
68+
69+
## We look forward to your contribution!

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
- [Prometheus](https://prometheus.io) metrics
2323
- [OpenTelemetry](https://opentelemetry.io) traces
2424
- [Structlog](https://www.structlog.org/) logging with native **logging** module support
25-
- Integrations with [Litestar](https://litestar.dev), [FastAPI](https://fastapi.tiangolo.com) and [Starlette](https://www.starlette.io)
25+
- Integrations with [Litestar](https://litestar.dev), [FastAPI](https://fastapi.tiangolo.com), [Starlette](https://www.starlette.io) and [Aiohttp](https://docs.aiohttp.org/en/stable/web.html)
2626
- Logging support for [Uvicorn](https://www.uvicorn.org) and [Gunicorn](https://gunicorn.org) with custom **UvicornWorker**
2727

2828
> [!IMPORTANT]

docs/integrations/index.rst

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.. _FastAPI: https://fastapi.tiangolo.com
22
.. _Starlette: https://www.starlette.io
33
.. _Litestar: https://litestar.dev
4+
.. _Aiohttp: https://docs.aiohttp.org/en/stable/web.html
45
.. _real_world: https://github.com/draincoder/asgi-monitor/tree/master/examples/real_world
56

67
.. _integrations:
@@ -13,6 +14,7 @@ Integration with the following frameworks is **now** implemented:
1314
* FastAPI_
1415
* Starlette_
1516
* Litestar_
17+
* Aiohttp_
1618

1719
But other integrations are planned in the near future, and you can also implement your own through **PR**.
1820

@@ -36,8 +38,7 @@ These frameworks have the **same** integration api, so here I will show you how
3638
import uvicorn
3739
3840
39-
40-
def create_app() -> None:
41+
def create_app() -> FastAPI:
4142
configure_logging(level=logging.INFO, json_format=True, include_trace=False)
4243
4344
resource = Resource.create(
@@ -141,3 +142,64 @@ If you want to use **StructlogPlugin** from ``litestar.plugins.structlog`` toget
141142
:caption: Import processor for extract trace meta
142143
143144
from asgi_monitor.logging.trace_processor import extract_opentelemetry_trace_meta
145+
146+
147+
Aiohttp
148+
====================
149+
150+
Despite the fact that **Aiohttp** does **not support** the **ASGI**-interface, but it is still a popular asynchronous framework and we are happy to support it.
151+
152+
.. code-block:: python
153+
:caption: Configuring monitoring for the Aiohttp
154+
155+
import logging
156+
157+
from aiohttp.web import Application, run_app
158+
from asgi_monitor.integrations.aiohttp import MetricsConfig, TracingConfig, setup_metrics, setup_tracing
159+
from asgi_monitor.logging import configure_logging
160+
from asgi_monitor.logging.aiohttp import TraceAccessLogger
161+
from opentelemetry import trace
162+
from opentelemetry.sdk.resources import Resource
163+
from opentelemetry.sdk.trace import TracerProvider
164+
165+
logger = logging.getLogger(__name__)
166+
167+
168+
def create_app() -> Application:
169+
configure_logging(level=logging.INFO, json_format=True, include_trace=True)
170+
171+
resource = Resource.create(
172+
attributes={
173+
"service.name": "aiohttp",
174+
},
175+
)
176+
tracer_provider = TracerProvider(resource=resource)
177+
trace.set_tracer_provider(tracer_provider)
178+
179+
trace_config = TracingConfig(tracer_provider=tracer_provider)
180+
metrics_config = MetricsConfig(app_name="aiohttp")
181+
182+
app = Application()
183+
184+
setup_tracing(app=app, config=trace_config)
185+
setup_metrics(app=app, config=metrics_config) # Must be configured last
186+
187+
return app
188+
189+
190+
if __name__ == "__main__":
191+
run_app(create_app(), host="127.0.0.1", port=8000, access_log_class=TraceAccessLogger, access_log=logger)
192+
193+
194+
.. important::
195+
196+
``TraceAccessLogger`` add trace meta info in aiohttp request log.
197+
198+
199+
**Aiohttp** tracing is not **based** on an ``opentelemetry-asgi``, so the ``TracingConfig`` looks like this:
200+
201+
1. ``scope_span_details_extractor`` (**Callable[[Request], tuple[str, dict[str, Any]]]**) - Callback which should return a string and a tuple, representing the desired default span name and a dictionary with any additional span attributes to set.
202+
203+
2. ``meter_provider`` (**MeterProvider | None**) - Optional meter provider to use.
204+
205+
3. ``tracer_provider`` (**TracerProvider | None**) - Optional tracer provider to use.

docs/monitoring/tracing.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ Configuration
2929

3030
``BaseTracingConfig`` is a configuration class for the OpenTelemetry middleware, and it accepts the following arguments as input:
3131

32-
1. ``exclude_urls_env_key`` (**str**) - Key to use when checking whether a list of excluded urls is passed via ENV. Each integration module uses its own ``TracingConfig``, where the default value of the **metrics_prefix** corresponds to the name of the integration.
32+
1. ``exclude_urls_env_key`` (**str**) - Key to use when checking whether a list of excluded urls is passed via ENV. Each integration module uses its own ``TracingConfig``.
3333

34-
2. ``scope_span_details_extractor`` (**Callable[[Any], tuple[str, dict[str, Any]]]**) - Callback which should return a string and a tuple, representing the desired default span name and a dictionary with any additional span attributes to set. Each integration module uses its own ``TracingConfig``, where the default value of the **metrics_prefix** corresponds to the name of the integration.
34+
2. ``scope_span_details_extractor`` (**Callable[[Any], tuple[str, dict[str, Any]]]**) - Callback which should return a string and a tuple, representing the desired default span name and a dictionary with any additional span attributes to set. Each integration module uses its own ``TracingConfig``.
3535

3636
3. ``server_request_hook_handler`` (**Callable[[Span, dict], None] | None**) - Optional callback which is called with the server span and ASGI scope object for every incoming request.
3737

examples/aiohttp_app.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import asyncio
2+
import logging
3+
from datetime import datetime, timezone
4+
5+
from aiohttp.web import Application, Request, Response, run_app
6+
from opentelemetry import trace
7+
from opentelemetry.sdk.resources import Resource
8+
from opentelemetry.sdk.trace import TracerProvider
9+
10+
from asgi_monitor.integrations.aiohttp import MetricsConfig, TracingConfig, setup_metrics, setup_tracing
11+
from asgi_monitor.logging import configure_logging
12+
from asgi_monitor.logging.aiohttp import TraceAccessLogger
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
async def index(requests: Request) -> Response:
18+
logger.info("Start sleeping at %s", datetime.now(tz=timezone.utc))
19+
await asyncio.sleep(1)
20+
logger.info("Stopped sleeping at %s", datetime.now(tz=timezone.utc))
21+
return Response(text="OK")
22+
23+
24+
def create_app() -> Application:
25+
configure_logging(json_format=False, include_trace=True)
26+
27+
app = Application()
28+
app.router.add_get("/", index)
29+
30+
resource = Resource.create(
31+
attributes={
32+
"service.name": "aiohttp",
33+
},
34+
)
35+
tracer_provider = TracerProvider(resource=resource)
36+
trace.set_tracer_provider(tracer_provider)
37+
38+
trace_config = TracingConfig(tracer_provider=tracer_provider)
39+
metrics_config = MetricsConfig(app_name="aiohttp")
40+
41+
setup_tracing(app=app, config=trace_config)
42+
setup_metrics(app=app, config=metrics_config)
43+
44+
return app
45+
46+
47+
if __name__ == "__main__":
48+
app = create_app()
49+
run_app(app=app, host="127.0.0.1", port=8000, access_log_class=TraceAccessLogger, access_log=logger)

0 commit comments

Comments
 (0)