Skip to content

Commit 35e14d7

Browse files
committed
Getting rid of the global REGISTRY, refactoring
1 parent cc1cfa7 commit 35e14d7

File tree

16 files changed

+181
-108
lines changed

16 files changed

+181
-108
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pip install asgi-monitor
4646
```python
4747
import logging
4848

49-
from asgi_monitor.integrations.fastapi import setup_metrics
49+
from asgi_monitor.integrations.fastapi import MetricsConfig, setup_metrics
5050
from asgi_monitor.logging import configure_logging
5151
from asgi_monitor.logging.uvicorn import build_uvicorn_log_config
5252
from fastapi import FastAPI
@@ -58,9 +58,10 @@ app = FastAPI(debug=True)
5858

5959
def run_app() -> None:
6060
log_config = build_uvicorn_log_config(level=logging.INFO, json_format=True, include_trace=False)
61+
metrics_config = MetricsConfig(app_name="fastapi")
6162

6263
configure_logging(level=logging.INFO, json_format=True, include_trace=False)
63-
setup_metrics(app, app_name="fastapi", include_metrics_endpoint=True, include_trace_exemplar=False)
64+
setup_metrics(app, metrics_config)
6465

6566
logger.info("App is ready to start")
6667

@@ -123,9 +124,9 @@ resource = Resource.create(
123124
tracer = TracerProvider(resource=resource)
124125
trace.set_tracer_provider(tracer)
125126
tracer.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="http://asgi-monitor.tempo:4317")))
126-
config = TracingConfig(tracer_provider=tracer)
127+
trace_config = TracingConfig(tracer_provider=tracer)
127128

128-
setup_tracing(app=app, config=config) # Must be configured last
129+
setup_tracing(app=app, config=trace_config) # Must be configured last
129130
```
130131

131132
1. Install the necessary exporter, for example [opentelemetry-exporter-jaeger](https://pypi.org/project/opentelemetry-exporter-jaeger/) or the standard [opentelemetry-exporter-otlp](https://pypi.org/project/opentelemetry-exporter-otlp/)

examples/fastapi_app.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from opentelemetry.sdk.resources import Resource
99
from opentelemetry.sdk.trace import TracerProvider
1010

11-
from asgi_monitor.integrations.fastapi import TracingConfig, setup_metrics, setup_tracing
11+
from asgi_monitor.integrations.fastapi import MetricsConfig, TracingConfig, setup_metrics, setup_tracing
1212
from asgi_monitor.logging import configure_logging
1313
from asgi_monitor.logging.uvicorn import build_uvicorn_log_config
1414

@@ -34,12 +34,15 @@ def create_app() -> FastAPI:
3434
)
3535
tracer = TracerProvider(resource=resource)
3636
trace.set_tracer_provider(tracer)
37-
config = TracingConfig(tracer_provider=tracer)
37+
38+
trace_config = TracingConfig(tracer_provider=tracer)
39+
metrics_config = MetricsConfig(app_name="fastapi", include_trace_exemplar=True)
3840

3941
app = FastAPI(debug=True)
4042
app.include_router(router)
41-
setup_metrics(app, app_name="fastapi", include_trace_exemplar=True, include_metrics_endpoint=True)
42-
setup_tracing(app=app, config=config)
43+
44+
setup_metrics(app=app, config=metrics_config)
45+
setup_tracing(app=app, config=trace_config)
4346

4447
return app
4548

examples/real_world/app/main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from opentelemetry.sdk.resources import Resource
88
from opentelemetry.sdk.trace import TracerProvider
99
from opentelemetry.sdk.trace.export import BatchSpanProcessor
10-
from asgi_monitor.integrations.fastapi import TracingConfig, setup_metrics, setup_tracing
10+
from asgi_monitor.integrations.fastapi import MetricsConfig, TracingConfig, setup_metrics, setup_tracing
1111
from asgi_monitor.logging import configure_logging
1212
from asgi_monitor.logging.uvicorn import build_uvicorn_log_config
1313

@@ -33,11 +33,14 @@ def create_app() -> FastAPI:
3333
tracer = TracerProvider(resource=resource)
3434
trace.set_tracer_provider(tracer)
3535
tracer.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=GRPC_ENDPOINT)))
36-
config = TracingConfig(tracer_provider=tracer)
36+
37+
trace_config = TracingConfig(tracer_provider=tracer)
38+
metrics_config = MetricsConfig(app_name=APP_NAME, include_trace_exemplar=True)
3739

3840
app = FastAPI(debug=True)
39-
setup_metrics(app=app, app_name=APP_NAME, include_trace_exemplar=True, include_metrics_endpoint=True)
40-
setup_tracing(app=app, config=config)
41+
42+
setup_metrics(app=app, config=metrics_config)
43+
setup_tracing(app=app, config=trace_config)
4144
setup_routes(app=app)
4245

4346
return app
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
uvicorn
22
fastapi
3-
asgi-monitor
3+
asgi-monitor @ git+https://github.com/draincoder/asgi-monitor@develop
44
opentelemetry-exporter-otlp

examples/starlette_app.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from starlette.responses import PlainTextResponse
1212
from starlette.routing import Route
1313

14-
from asgi_monitor.integrations.starlette import TracingConfig, setup_metrics, setup_tracing
14+
from asgi_monitor.integrations.starlette import MetricsConfig, TracingConfig, setup_metrics, setup_tracing
1515
from asgi_monitor.logging import configure_logging
1616
from asgi_monitor.logging.uvicorn import build_uvicorn_log_config
1717

@@ -35,11 +35,14 @@ def create_app() -> Starlette:
3535
)
3636
tracer = TracerProvider(resource=resource)
3737
trace.set_tracer_provider(tracer)
38-
config = TracingConfig(tracer_provider=tracer)
38+
39+
trace_config = TracingConfig(tracer_provider=tracer)
40+
metrics_config = MetricsConfig(app_name="starlette", include_trace_exemplar=True)
3941

4042
app = Starlette(debug=True, routes=[Route("/", endpoint=index, methods=["GET"])])
41-
setup_metrics(app, app_name="starlette", include_trace_exemplar=True, include_metrics_endpoint=True)
42-
setup_tracing(app=app, config=config)
43+
44+
setup_metrics(app=app, config=metrics_config)
45+
setup_tracing(app=app, config=trace_config)
4346

4447
return app
4548

src/asgi_monitor/integrations/fastapi.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,39 @@
1212
_get_default_span_details,
1313
get_metrics,
1414
)
15+
from asgi_monitor.metrics.config import CommonMetricsConfig
16+
from asgi_monitor.metrics.container import MetricsContainer
17+
from asgi_monitor.tracing import CommonTracingConfig
1518

1619
__all__ = (
1720
"TracingConfig",
1821
"TracingMiddleware",
19-
"MetricsMiddleware",
2022
"setup_tracing",
23+
"MetricsConfig",
24+
"MetricsMiddleware",
2125
"setup_metrics",
2226
)
2327

2428

25-
from asgi_monitor.tracing import CommonTracingConfig
29+
@dataclass
30+
class MetricsConfig(CommonMetricsConfig):
31+
"""Configuration class for the Metrics middleware."""
32+
33+
metrics_prefix: str = "fastapi"
34+
"""The prefix to use for the metrics."""
2635

2736

2837
@dataclass
2938
class TracingConfig(CommonTracingConfig):
30-
"""Configuration class for the OpenTelemetry middleware.
39+
"""
40+
Configuration class for the OpenTelemetry middleware.
3141
Consult the OpenTelemetry ASGI documentation for more info about the configuration options.
3242
https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html
3343
"""
3444

3545
exclude_urls_env_key: str = "FASTAPI"
36-
"""Key to use when checking whether a list of excluded urls is passed via ENV.
46+
"""
47+
Key to use when checking whether a list of excluded urls is passed via ENV.
3748
OpenTelemetry supports excluding urls by passing an env in the format '{exclude_urls_env_key}_EXCLUDED_URLS'.
3849
"""
3950

@@ -57,34 +68,24 @@ def setup_tracing(app: FastAPI, config: TracingConfig) -> None:
5768
app.add_middleware(TracingMiddleware, config=config)
5869

5970

60-
def setup_metrics(
61-
app: FastAPI,
62-
app_name: str,
63-
metrics_prefix: str = "fastapi",
64-
*,
65-
include_trace_exemplar: bool,
66-
include_metrics_endpoint: bool,
67-
) -> None:
71+
def setup_metrics(app: FastAPI, config: MetricsConfig) -> None:
6872
"""
6973
Set up metrics for a FastAPI application.
7074
This function adds a MetricsMiddleware to the FastAPI application with the specified parameters.
71-
If include_metrics_endpoint is True, it also adds a route for "/metrics" that returns Prometheus default metrics.
7275
73-
:param FastAPI app: The FastAPI application instance.
74-
:param str app_name: The name of the FastAPI application.
75-
:param str metrics_prefix: The prefix to use for the metrics (default is "fastapi").
76-
:param bool include_trace_exemplar: Whether to include trace exemplars in the metrics.
77-
:param bool include_metrics_endpoint: Whether to include a /metrics endpoint.
76+
:param FastAPI app: The Starlette application instance.
77+
:param MetricsConfig config: Configuration for the metrics.
7878
:returns: None
7979
"""
8080

81+
app.state.metrics_registry = config.registry
8182
app.add_middleware(
8283
MetricsMiddleware,
83-
app_name=app_name,
84-
metrics_prefix=metrics_prefix,
85-
include_trace_exemplar=include_trace_exemplar,
84+
app_name=config.app_name,
85+
container=MetricsContainer(config.metrics_prefix, config.registry),
86+
include_trace_exemplar=config.include_trace_exemplar,
8687
)
87-
if include_metrics_endpoint:
88+
if config.include_metrics_endpoint:
8889
app.add_route(
8990
path="/metrics",
9091
route=get_metrics,

src/asgi_monitor/integrations/starlette.py

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,24 @@
1111
from starlette.routing import Match
1212
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
1313

14-
from asgi_monitor.metrics import get_latest_metrics
15-
from asgi_monitor.metrics.container import MetricsContainer
16-
from asgi_monitor.metrics.manager import MetricsManager
17-
1814
if TYPE_CHECKING:
1915
from starlette.applications import Starlette
2016
from starlette.requests import Request
2117
from starlette.types import ASGIApp, Receive, Scope, Send
2218

19+
from asgi_monitor.metrics import get_latest_metrics
20+
from asgi_monitor.metrics.config import CommonMetricsConfig
21+
from asgi_monitor.metrics.container import MetricsContainer
22+
from asgi_monitor.metrics.manager import MetricsManager
2323
from asgi_monitor.tracing.config import CommonTracingConfig
2424
from asgi_monitor.tracing.middleware import build_open_telemetry_middleware
2525

2626
__all__ = (
2727
"TracingConfig",
2828
"TracingMiddleware",
29-
"MetricsMiddleware",
3029
"setup_tracing",
30+
"MetricsConfig",
31+
"MetricsMiddleware",
3132
"setup_metrics",
3233
)
3334

@@ -69,13 +70,15 @@ def _get_path(request: Request) -> tuple[str, bool]:
6970

7071
@dataclass
7172
class TracingConfig(CommonTracingConfig):
72-
"""Configuration class for the OpenTelemetry middleware.
73+
"""
74+
Configuration class for the OpenTelemetry middleware.
7375
Consult the OpenTelemetry ASGI documentation for more info about the configuration options.
7476
https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html
7577
"""
7678

7779
exclude_urls_env_key: str = "STARLETTE"
78-
"""Key to use when checking whether a list of excluded urls is passed via ENV.
80+
"""
81+
Key to use when checking whether a list of excluded urls is passed via ENV.
7982
OpenTelemetry supports excluding urls by passing an env in the format '{exclude_urls_env_key}_EXCLUDED_URLS'.
8083
"""
8184

@@ -86,6 +89,14 @@ class TracingConfig(CommonTracingConfig):
8689
"""
8790

8891

92+
@dataclass
93+
class MetricsConfig(CommonMetricsConfig):
94+
"""Configuration class for the Metrics middleware."""
95+
96+
metrics_prefix: str = "starlette"
97+
"""The prefix to use for the metrics."""
98+
99+
89100
class TracingMiddleware:
90101
def __init__(self, app: ASGIApp, config: TracingConfig) -> None:
91102
self.app = app
@@ -108,12 +119,11 @@ def __init__(
108119
self,
109120
app: ASGIApp,
110121
app_name: str,
111-
metrics_prefix: str,
122+
container: MetricsContainer,
112123
*,
113124
include_trace_exemplar: bool,
114125
) -> None:
115126
super().__init__(app)
116-
container = MetricsContainer(prefix=metrics_prefix)
117127
self.metrics = MetricsManager(app_name=app_name, container=container)
118128
self.include_exemplar = include_trace_exemplar
119129
self.metrics.add_app_info()
@@ -166,7 +176,8 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -
166176

167177

168178
async def get_metrics(request: Request) -> Response:
169-
response = get_latest_metrics(openmetrics_format=False)
179+
registry = request.app.state.metrics_registry
180+
response = get_latest_metrics(registry, openmetrics_format=False)
170181
return Response(
171182
content=response.payload,
172183
status_code=response.status_code,
@@ -187,34 +198,24 @@ def setup_tracing(app: Starlette, config: TracingConfig) -> None:
187198
app.add_middleware(TracingMiddleware, config=config)
188199

189200

190-
def setup_metrics(
191-
app: Starlette,
192-
app_name: str,
193-
metrics_prefix: str = "starlette",
194-
*,
195-
include_trace_exemplar: bool,
196-
include_metrics_endpoint: bool,
197-
) -> None:
201+
def setup_metrics(app: Starlette, config: MetricsConfig) -> None:
198202
"""
199203
Set up metrics for a Starlette application.
200204
This function adds a MetricsMiddleware to the Starlette application with the specified parameters.
201-
If include_metrics_endpoint is True, it also adds a route for "/metrics" that returns Prometheus default metrics.
202205
203206
:param Starlette app: The Starlette application instance.
204-
:param str app_name: The name of the Starlette application.
205-
:param str metrics_prefix: The prefix to use for the metrics (default is "starlette").
206-
:param bool include_trace_exemplar: Whether to include trace exemplars in the metrics.
207-
:param bool include_metrics_endpoint: Whether to include a /metrics endpoint.
207+
:param MetricsConfig config: Configuration for the metrics.
208208
:returns: None
209209
"""
210210

211+
app.state.metrics_registry = config.registry
211212
app.add_middleware(
212213
MetricsMiddleware,
213-
app_name=app_name,
214-
metrics_prefix=metrics_prefix,
215-
include_trace_exemplar=include_trace_exemplar,
214+
app_name=config.app_name,
215+
container=MetricsContainer(config.metrics_prefix, config.registry),
216+
include_trace_exemplar=config.include_trace_exemplar,
216217
)
217-
if include_metrics_endpoint:
218+
if config.include_metrics_endpoint:
218219
app.add_route(
219220
path="/metrics",
220221
route=get_metrics,

src/asgi_monitor/metrics/config.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from dataclasses import dataclass, field
2+
3+
from prometheus_client import CollectorRegistry
4+
5+
__all__ = ("CommonMetricsConfig",)
6+
7+
8+
def _build_default_registry() -> CollectorRegistry:
9+
return CollectorRegistry(auto_describe=True)
10+
11+
12+
@dataclass
13+
class CommonMetricsConfig:
14+
"""Configuration class for the Metrics middleware."""
15+
16+
app_name: str
17+
"""The name of the ASGI application."""
18+
19+
metrics_prefix: str
20+
"""The prefix to use for the metrics."""
21+
22+
registry: CollectorRegistry = field(default_factory=_build_default_registry)
23+
"""A registry for metrics, you can also specify a global REGISTRY to support all your current metrics"""
24+
25+
include_trace_exemplar: bool = field(default=False)
26+
"""Whether to include trace exemplars in the metrics."""
27+
28+
include_metrics_endpoint: bool = field(default=True)
29+
"""Whether to include a /metrics endpoint."""

0 commit comments

Comments
 (0)