Skip to content

Commit 47b9843

Browse files
committed
Add real world example
1 parent a728fa3 commit 47b9843

File tree

21 files changed

+1432
-6
lines changed

21 files changed

+1432
-6
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ repos:
2323
- id: ensure-dunder-all
2424
exclude: "tests*|examples*"
2525
args: ["--use-tuple"]
26-
- repo: https://github.com/charliermarsh/ruff-pre-commit
27-
rev: v0.2.2
26+
- repo: https://github.com/astral-sh/ruff-pre-commit
27+
rev: v0.3.0
2828
hooks:
2929
- id: ruff
3030
args: ["--fix"]

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## ASGI Monitor
22
[![license](https://img.shields.io/github/license/draincoder/asgi-monitor)](https://github.com/draincoder/asgi-monitor/blob/master/LICENSE)
33
[![test](https://github.com/draincoder/asgi-monitor/actions/workflows/ci.yaml/badge.svg)](https://github.com/draincoder/asgi-monitor/actions/workflows/ci.yaml)
4+
[![PyPI version](https://badge.fury.io/py/asgi-monitor.svg)](https://pypi.python.org/pypi/asgi-monitor)
5+
[![Supported versions](https://img.shields.io/pypi/pyversions/asgi-monitor.svg)](https://pypi.python.org/pypi/asgi-monitor)
6+
[![downloads](https://img.shields.io/pypi/dm/asgi-monitor.svg)](https://pypistats.org/packages/asgi-monitor)
47

58
A library for easy and fast configuration of logging, tracing and monitoring of ASGI applications.

examples/gunicorn_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def run() -> None:
1616
}
1717
configure_logging(level=level, json_format=True)
1818

19-
from examples.asgi_app import get_app
19+
from asgi_app import get_app
2020

2121
app = get_app()
2222

examples/real_world/Dockerfile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
FROM python:3.10-slim-buster as python-base
2+
3+
ENV PYTHONUNBUFFERED=1 \
4+
PYTHONDONTWRITEBYTECODE=1 \
5+
PIP_NO_CACHE_DIR=off \
6+
PIP_DISABLE_PIP_VERSION_CHECK=on \
7+
PIP_DEFAULT_TIMEOUT=100 \
8+
PYSETUP_PATH="/opt/pysetup" \
9+
VENV_PATH="/opt/pysetup/.venv"
10+
11+
RUN python3 -m venv $VENV_PATH
12+
ENV PATH="$VENV_PATH/bin:$PATH"
13+
14+
FROM python-base as builder-base
15+
RUN apt-get update && apt-get install -y gcc git
16+
17+
WORKDIR $PYSETUP_PATH
18+
COPY requirements.txt .
19+
20+
RUN pip install --no-cache-dir --upgrade pip \
21+
&& pip install --no-cache-dir setuptools wheel \
22+
&& pip install --no-cache-dir -r requirements.txt
23+
24+
FROM python-base as production
25+
COPY --from=builder-base $PYSETUP_PATH $PYSETUP_PATH
26+
RUN apt-get update && apt-get install -y curl
27+
28+
WORKDIR /app
29+
COPY ./app /app/app
30+
31+
ENTRYPOINT ["python", "-Om", "app.main"]

examples/real_world/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Start example in Docker
2+
3+
```shell
4+
docker compose -f examples/real_world/docker-compose.yaml --profile api --profile grafana up --build -d
5+
```
6+
7+
## Stop example in Docker
8+
9+
```shell
10+
docker compose -f examples/real_world/docker-compose.yaml --profile api --profile grafana down
11+
```
12+
13+
## Interfaces
14+
15+
1. Grafana - http://127.0.0.1:3000/
16+
2. Swagger - http://127.0.0.1:8080/docs/
File renamed without changes.

examples/real_world/app/main.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import logging
2+
3+
import uvicorn
4+
from fastapi import FastAPI
5+
from opentelemetry import trace
6+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
7+
from opentelemetry.sdk.resources import Resource
8+
from opentelemetry.sdk.trace import TracerProvider
9+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
10+
from asgi_monitor.integrations.fastapi import TracingConfig, setup_metrics, setup_tracing
11+
from asgi_monitor.logging import configure_logging
12+
from asgi_monitor.logging.uvicorn import build_uvicorn_log_config
13+
14+
from app.routes import setup_routes
15+
16+
logger = logging.getLogger(__name__)
17+
18+
APP_NAME = "asgi-monitor"
19+
HOST = "0.0.0.0"
20+
PORT = 8080
21+
GRPC_ENDPOINT = "http://asgi-monitor.tempo:4317"
22+
23+
24+
def create_app() -> FastAPI:
25+
configure_logging(level=logging.INFO, json_format=True)
26+
27+
resource = Resource.create(
28+
attributes={
29+
"service.name": APP_NAME,
30+
"compose_service": APP_NAME,
31+
},
32+
)
33+
tracer = TracerProvider(resource=resource)
34+
trace.set_tracer_provider(tracer)
35+
tracer.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=GRPC_ENDPOINT)))
36+
config = TracingConfig(tracer_provider=tracer)
37+
38+
app = FastAPI(debug=True)
39+
setup_metrics(app, app_name=APP_NAME, include_trace_exemplar=True, include_metrics_endpoint=True)
40+
setup_tracing(app=app, config=config)
41+
setup_routes(app=app)
42+
43+
return app
44+
45+
46+
if __name__ == "__main__":
47+
log_config = build_uvicorn_log_config(
48+
level=logging.INFO,
49+
json_format=True,
50+
include_trace=True,
51+
)
52+
uvicorn.run(create_app(), host=HOST, port=PORT, log_config=log_config)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from fastapi import FastAPI
2+
3+
from .error import error_router
4+
from .healthcheck import healthcheck_router
5+
from .slow import slow_router
6+
7+
__all__ = ("setup_routes",)
8+
9+
10+
def setup_routes(app: FastAPI) -> None:
11+
app.include_router(healthcheck_router)
12+
app.include_router(error_router)
13+
app.include_router(slow_router)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import logging
2+
3+
from fastapi import APIRouter, status
4+
from fastapi.exceptions import HTTPException
5+
6+
error_router = APIRouter(
7+
prefix="/error",
8+
tags=["Error"],
9+
include_in_schema=True,
10+
)
11+
logger = logging.getLogger(__name__)
12+
13+
14+
@error_router.get("/500", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
15+
async def get_500_error() -> None:
16+
logger.error("Internal Server Error Occurred", extra={"status_code": 500})
17+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Internal Server Error")
18+
19+
20+
@error_router.get("/404", status_code=status.HTTP_404_NOT_FOUND)
21+
async def get_404() -> None:
22+
logger.error("Not Found", extra={"status_code": 404})
23+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not Found")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from fastapi import APIRouter, status
2+
3+
healthcheck_router = APIRouter(
4+
prefix="/healthcheck",
5+
tags=["Healthcheck"],
6+
include_in_schema=True,
7+
)
8+
9+
10+
@healthcheck_router.get("/", status_code=status.HTTP_200_OK)
11+
async def get_status() -> dict:
12+
return {"message": "ok", "status": "success"}

0 commit comments

Comments
 (0)