microbootstrap 1.6.0__tar.gz → 1.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/PKG-INFO +47 -4
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/README.md +46 -3
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/bootstrappers/fastmcp.py +72 -3
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/bootstrappers/litestar.py +14 -14
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/opentelemetry_instrument.py +26 -10
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/settings.py +1 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/pyproject.toml +2 -1
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/pyproject.toml.orig +2 -1
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/__init__.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/bootstrappers/__init__.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/bootstrappers/base.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/bootstrappers/fastapi.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/bootstrappers/faststream.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/config/__init__.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/config/fastapi.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/config/fastmcp.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/config/faststream.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/config/litestar.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/console_writer.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/exceptions.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/granian_server.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/helpers.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/__init__.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/base.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/cors_instrument.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/health_checks_instrument.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/instrument_box.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/logging_instrument.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/openapi_security_schemes.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/openapi_version_docs.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/prometheus_instrument.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/pyroscope_instrument.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/sentry_instrument.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/swagger_instrument.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments_setupper.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/middlewares/__init__.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/middlewares/fastapi.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/middlewares/fastmcp.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/middlewares/faststream.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/middlewares/litestar.py +0 -0
- {microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: microbootstrap
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Package for bootstrapping new micro-services
|
|
5
5
|
Keywords: python,microservice,bootstrap,opentelemetry,logging,error-tracing,litestar,fastapi,fastmcp,mcp
|
|
6
6
|
Author: community-of-python
|
|
@@ -505,7 +505,7 @@ class YourSettings(BaseServiceSettings):
|
|
|
505
505
|
opentelemetry_namespace: str | None = None
|
|
506
506
|
opentelemetry_insecure: bool = True
|
|
507
507
|
opentelemetry_instrumentors: list[OpenTelemetryInstrumentor] = []
|
|
508
|
-
opentelemetry_exclude_urls: list[str] = []
|
|
508
|
+
opentelemetry_exclude_urls: list[str] = ["/metrics"]
|
|
509
509
|
opentelemetry_baggage_span_attributes: dict[str, str] = {}
|
|
510
510
|
|
|
511
511
|
... # Other settings here
|
|
@@ -521,9 +521,9 @@ Parameters description:
|
|
|
521
521
|
- `opentelemetry_insecure` - is opentelemetry connection secure.
|
|
522
522
|
- `opentelemetry_container_name` - will be passed to the `Resource`.
|
|
523
523
|
- `opentelemetry_instrumentors` - a list of extra instrumentors.
|
|
524
|
-
- `opentelemetry_exclude_urls` - list of
|
|
524
|
+
- `opentelemetry_exclude_urls` - list of url regexes that produce no server spans (`["/metrics"]` by default). For Litestar they are combined with `OTEL_PYTHON_LITESTAR_EXCLUDED_URLS` (or `OTEL_PYTHON_EXCLUDED_URLS`), for FastMCP with `OTEL_PYTHON_STARLETTE_EXCLUDED_URLS` (or `OTEL_PYTHON_EXCLUDED_URLS`).
|
|
525
525
|
- `opentelemetry_log_traces` - traces will be logged to stdout.
|
|
526
|
-
- `opentelemetry_generate_health_check_spans` - generate spans for health check handlers if `True`
|
|
526
|
+
- `opentelemetry_generate_health_check_spans` - generate spans for health check handlers if `True`; if `False`, `health_checks_path` is added to the excluded urls.
|
|
527
527
|
- `opentelemetry_baggage_span_attributes` - maps allowed baggage keys to attributes added to local server and consumer spans.
|
|
528
528
|
|
|
529
529
|
These settings are subsequently passed to [opentelemetry](https://opentelemetry.io/), finalizing your Opentelemetry integration.
|
|
@@ -559,6 +559,49 @@ class YourSettings(FastStreamSettings):
|
|
|
559
559
|
...
|
|
560
560
|
```
|
|
561
561
|
|
|
562
|
+
#### FastMCP
|
|
563
|
+
|
|
564
|
+
`FastMcpSettings` include all OpenTelemetry settings, so tracing is enabled with `opentelemetry_endpoint` alone:
|
|
565
|
+
|
|
566
|
+
```python
|
|
567
|
+
from fastmcp import FastMCP
|
|
568
|
+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
|
569
|
+
|
|
570
|
+
from microbootstrap import FastMcpSettings
|
|
571
|
+
from microbootstrap.bootstrappers.fastmcp import FastMcpBootstrapper
|
|
572
|
+
from microbootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrumentor
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class YourSettings(FastMcpSettings):
|
|
576
|
+
opentelemetry_endpoint: str | None = "otel-collector:4317"
|
|
577
|
+
opentelemetry_instrumentors: list[OpenTelemetryInstrumentor] = [OpenTelemetryInstrumentor(HTTPXClientInstrumentor())]
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
application: FastMCP = FastMcpBootstrapper(YourSettings()).bootstrap()
|
|
581
|
+
http_application = application.http_app(path="/mcp")
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
FastMCP creates its ASGI application only when `http_app()` is called (directly or by `application.run(transport="http")`),
|
|
585
|
+
so `SERVER` spans are added to every application returned by `http_app()`. Each request to it is wrapped in
|
|
586
|
+
[`OpenTelemetryMiddleware`](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html)
|
|
587
|
+
and produces a span named like `POST /mcp` or `GET /health/` with the `http.route` attribute and the response status code.
|
|
588
|
+
Requests to unknown paths produce spans named after the HTTP method only, without `http.route`.
|
|
589
|
+
|
|
590
|
+
- `opentelemetry_endpoint` - OTLP endpoint for exported traces.
|
|
591
|
+
- `opentelemetry_instrumentors` - extra instrumentors, e.g. for HTTP clients used by your tools.
|
|
592
|
+
- `opentelemetry_exclude_urls` - urls without spans, `["/metrics"]` by default. Combined with `OTEL_PYTHON_STARLETTE_EXCLUDED_URLS`.
|
|
593
|
+
- `opentelemetry_generate_health_check_spans` - set to `False` to skip spans for `health_checks_path`.
|
|
594
|
+
- The status code attribute name depends on `OTEL_SEMCONV_STABILITY_OPT_IN`: `http.status_code` when unset,
|
|
595
|
+
`http.response.status_code` for `http`, both for `http/dup`.
|
|
596
|
+
|
|
597
|
+
Applications already instrumented by `StarletteInstrumentor` are left as is, so the request is never traced twice.
|
|
598
|
+
To post-process every created ASGI application yourself, use `add_http_application_postprocessor` on the bootstrapped application:
|
|
599
|
+
|
|
600
|
+
```python
|
|
601
|
+
application = FastMcpBootstrapper(settings).bootstrap()
|
|
602
|
+
application.add_http_application_postprocessor(lambda http_application: http_application)
|
|
603
|
+
```
|
|
604
|
+
|
|
562
605
|
### [Pyroscope](https://pyroscope.io)
|
|
563
606
|
|
|
564
607
|
To integrate Pyroscope, specify the `pyroscope_endpoint`.
|
|
@@ -440,7 +440,7 @@ class YourSettings(BaseServiceSettings):
|
|
|
440
440
|
opentelemetry_namespace: str | None = None
|
|
441
441
|
opentelemetry_insecure: bool = True
|
|
442
442
|
opentelemetry_instrumentors: list[OpenTelemetryInstrumentor] = []
|
|
443
|
-
opentelemetry_exclude_urls: list[str] = []
|
|
443
|
+
opentelemetry_exclude_urls: list[str] = ["/metrics"]
|
|
444
444
|
opentelemetry_baggage_span_attributes: dict[str, str] = {}
|
|
445
445
|
|
|
446
446
|
... # Other settings here
|
|
@@ -456,9 +456,9 @@ Parameters description:
|
|
|
456
456
|
- `opentelemetry_insecure` - is opentelemetry connection secure.
|
|
457
457
|
- `opentelemetry_container_name` - will be passed to the `Resource`.
|
|
458
458
|
- `opentelemetry_instrumentors` - a list of extra instrumentors.
|
|
459
|
-
- `opentelemetry_exclude_urls` - list of
|
|
459
|
+
- `opentelemetry_exclude_urls` - list of url regexes that produce no server spans (`["/metrics"]` by default). For Litestar they are combined with `OTEL_PYTHON_LITESTAR_EXCLUDED_URLS` (or `OTEL_PYTHON_EXCLUDED_URLS`), for FastMCP with `OTEL_PYTHON_STARLETTE_EXCLUDED_URLS` (or `OTEL_PYTHON_EXCLUDED_URLS`).
|
|
460
460
|
- `opentelemetry_log_traces` - traces will be logged to stdout.
|
|
461
|
-
- `opentelemetry_generate_health_check_spans` - generate spans for health check handlers if `True`
|
|
461
|
+
- `opentelemetry_generate_health_check_spans` - generate spans for health check handlers if `True`; if `False`, `health_checks_path` is added to the excluded urls.
|
|
462
462
|
- `opentelemetry_baggage_span_attributes` - maps allowed baggage keys to attributes added to local server and consumer spans.
|
|
463
463
|
|
|
464
464
|
These settings are subsequently passed to [opentelemetry](https://opentelemetry.io/), finalizing your Opentelemetry integration.
|
|
@@ -494,6 +494,49 @@ class YourSettings(FastStreamSettings):
|
|
|
494
494
|
...
|
|
495
495
|
```
|
|
496
496
|
|
|
497
|
+
#### FastMCP
|
|
498
|
+
|
|
499
|
+
`FastMcpSettings` include all OpenTelemetry settings, so tracing is enabled with `opentelemetry_endpoint` alone:
|
|
500
|
+
|
|
501
|
+
```python
|
|
502
|
+
from fastmcp import FastMCP
|
|
503
|
+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
|
504
|
+
|
|
505
|
+
from microbootstrap import FastMcpSettings
|
|
506
|
+
from microbootstrap.bootstrappers.fastmcp import FastMcpBootstrapper
|
|
507
|
+
from microbootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrumentor
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class YourSettings(FastMcpSettings):
|
|
511
|
+
opentelemetry_endpoint: str | None = "otel-collector:4317"
|
|
512
|
+
opentelemetry_instrumentors: list[OpenTelemetryInstrumentor] = [OpenTelemetryInstrumentor(HTTPXClientInstrumentor())]
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
application: FastMCP = FastMcpBootstrapper(YourSettings()).bootstrap()
|
|
516
|
+
http_application = application.http_app(path="/mcp")
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
FastMCP creates its ASGI application only when `http_app()` is called (directly or by `application.run(transport="http")`),
|
|
520
|
+
so `SERVER` spans are added to every application returned by `http_app()`. Each request to it is wrapped in
|
|
521
|
+
[`OpenTelemetryMiddleware`](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html)
|
|
522
|
+
and produces a span named like `POST /mcp` or `GET /health/` with the `http.route` attribute and the response status code.
|
|
523
|
+
Requests to unknown paths produce spans named after the HTTP method only, without `http.route`.
|
|
524
|
+
|
|
525
|
+
- `opentelemetry_endpoint` - OTLP endpoint for exported traces.
|
|
526
|
+
- `opentelemetry_instrumentors` - extra instrumentors, e.g. for HTTP clients used by your tools.
|
|
527
|
+
- `opentelemetry_exclude_urls` - urls without spans, `["/metrics"]` by default. Combined with `OTEL_PYTHON_STARLETTE_EXCLUDED_URLS`.
|
|
528
|
+
- `opentelemetry_generate_health_check_spans` - set to `False` to skip spans for `health_checks_path`.
|
|
529
|
+
- The status code attribute name depends on `OTEL_SEMCONV_STABILITY_OPT_IN`: `http.status_code` when unset,
|
|
530
|
+
`http.response.status_code` for `http`, both for `http/dup`.
|
|
531
|
+
|
|
532
|
+
Applications already instrumented by `StarletteInstrumentor` are left as is, so the request is never traced twice.
|
|
533
|
+
To post-process every created ASGI application yourself, use `add_http_application_postprocessor` on the bootstrapped application:
|
|
534
|
+
|
|
535
|
+
```python
|
|
536
|
+
application = FastMcpBootstrapper(settings).bootstrap()
|
|
537
|
+
application.add_http_application_postprocessor(lambda http_application: http_application)
|
|
538
|
+
```
|
|
539
|
+
|
|
497
540
|
### [Pyroscope](https://pyroscope.io)
|
|
498
541
|
|
|
499
542
|
To integrate Pyroscope, specify the `pyroscope_endpoint`.
|
|
@@ -4,10 +4,15 @@ import typing
|
|
|
4
4
|
import prometheus_client
|
|
5
5
|
import typing_extensions
|
|
6
6
|
from fastmcp import FastMCP
|
|
7
|
+
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
|
8
|
+
from opentelemetry.util.http import ExcludeList, get_excluded_urls
|
|
9
|
+
from starlette.applications import Starlette
|
|
7
10
|
from starlette.responses import JSONResponse, Response
|
|
11
|
+
from starlette.routing import Match, Mount, Route
|
|
8
12
|
|
|
9
13
|
from microbootstrap.bootstrappers.base import ApplicationBootstrapper
|
|
10
14
|
from microbootstrap.config.fastmcp import FastMcpConfig
|
|
15
|
+
from microbootstrap.instruments import opentelemetry_instrument
|
|
11
16
|
from microbootstrap.instruments.health_checks_instrument import HealthChecksInstrument, HealthCheckTypedDict
|
|
12
17
|
from microbootstrap.instruments.logging_instrument import LoggingInstrument
|
|
13
18
|
from microbootstrap.instruments.prometheus_instrument import FastMcpPrometheusConfig, PrometheusInstrument
|
|
@@ -18,16 +23,46 @@ from microbootstrap.settings import FastMcpSettings
|
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
if typing.TYPE_CHECKING:
|
|
26
|
+
from fastmcp.server.http import StarletteWithLifespan
|
|
21
27
|
from starlette.requests import Request
|
|
28
|
+
from starlette.types import Scope
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
StarletteT = typing.TypeVar("StarletteT", bound=Starlette)
|
|
22
32
|
|
|
23
33
|
|
|
24
34
|
class KwargsFastMCP(FastMCP[typing.Any]):
|
|
25
35
|
def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
|
|
26
36
|
super().__init__(**kwargs)
|
|
37
|
+
self.http_application_postprocessors: list[typing.Callable[[StarletteWithLifespan], StarletteWithLifespan]] = []
|
|
38
|
+
|
|
39
|
+
def add_http_application_postprocessor(
|
|
40
|
+
self, postprocessor: typing.Callable[[StarletteWithLifespan], StarletteWithLifespan]
|
|
41
|
+
) -> None:
|
|
42
|
+
self.http_application_postprocessors.append(postprocessor)
|
|
43
|
+
|
|
44
|
+
def http_app(self, *args: typing.Any, **kwargs: typing.Any) -> StarletteWithLifespan: # noqa: ANN401
|
|
45
|
+
# ASGI application is created by the user after bootstrap, so instruments subscribe to its creation
|
|
46
|
+
http_application = super().http_app(*args, **kwargs)
|
|
47
|
+
for postprocessor in self.http_application_postprocessors:
|
|
48
|
+
http_application = postprocessor(http_application)
|
|
49
|
+
return http_application
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_fastmcp_route_details_from_scope(
|
|
53
|
+
scope: Scope,
|
|
54
|
+
routes: typing.Iterable[typing.Any],
|
|
55
|
+
) -> tuple[str, dict[str, str]]:
|
|
56
|
+
method: typing.Final = str(scope.get("method", "HTTP")).strip()
|
|
57
|
+
for route in routes:
|
|
58
|
+
if isinstance(route, (Route, Mount)) and route.matches(scope)[0] == Match.FULL:
|
|
59
|
+
return opentelemetry_instrument.build_span_name(method, route.path), {"http.route": route.path}
|
|
60
|
+
# Unmatched paths get no `http.route` to keep its cardinality low
|
|
61
|
+
return method, {}
|
|
27
62
|
|
|
28
63
|
|
|
29
64
|
class FastMcpBootstrapper(
|
|
30
|
-
ApplicationBootstrapper[FastMcpSettings,
|
|
65
|
+
ApplicationBootstrapper[FastMcpSettings, KwargsFastMCP, FastMcpConfig],
|
|
31
66
|
):
|
|
32
67
|
application_config = FastMcpConfig()
|
|
33
68
|
application_type = KwargsFastMCP
|
|
@@ -41,8 +76,8 @@ class FastMcpBootstrapper(
|
|
|
41
76
|
|
|
42
77
|
def bootstrap_before_instruments_after_app_created(
|
|
43
78
|
self,
|
|
44
|
-
application:
|
|
45
|
-
) ->
|
|
79
|
+
application: KwargsFastMCP,
|
|
80
|
+
) -> KwargsFastMCP:
|
|
46
81
|
self.console_writer.print_bootstrap_table()
|
|
47
82
|
return application
|
|
48
83
|
|
|
@@ -51,6 +86,40 @@ FastMcpBootstrapper.use_instrument()(SentryInstrument)
|
|
|
51
86
|
FastMcpBootstrapper.use_instrument()(PyroscopeInstrument)
|
|
52
87
|
|
|
53
88
|
|
|
89
|
+
@FastMcpBootstrapper.use_instrument()
|
|
90
|
+
class FastMcpOpentelemetryInstrument(
|
|
91
|
+
opentelemetry_instrument.BaseOpentelemetryInstrument[opentelemetry_instrument.OpentelemetryConfig]
|
|
92
|
+
):
|
|
93
|
+
def bootstrap_after(self, application: FastMCP[typing.Any]) -> FastMCP[typing.Any]: # type: ignore[override]
|
|
94
|
+
if isinstance(application, KwargsFastMCP):
|
|
95
|
+
application.add_http_application_postprocessor(self.__instrument_http_app)
|
|
96
|
+
return application
|
|
97
|
+
|
|
98
|
+
def __instrument_http_app(self, http_application: StarletteT) -> StarletteT:
|
|
99
|
+
# `StarletteInstrumentor` marks applications the same way, so each application is instrumented once
|
|
100
|
+
if getattr(http_application, "_is_instrumented_by_opentelemetry", False):
|
|
101
|
+
return http_application
|
|
102
|
+
|
|
103
|
+
def build_route_details(scope: Scope) -> tuple[str, dict[str, str]]:
|
|
104
|
+
return build_fastmcp_route_details_from_scope(scope, http_application.routes)
|
|
105
|
+
|
|
106
|
+
http_application.add_middleware(
|
|
107
|
+
OpenTelemetryMiddleware,
|
|
108
|
+
tracer_provider=self.tracer_provider,
|
|
109
|
+
default_span_details=build_route_details,
|
|
110
|
+
excluded_urls=opentelemetry_instrument.CombinedExcludeList(
|
|
111
|
+
ExcludeList(self.define_exclude_urls()),
|
|
112
|
+
get_excluded_urls("STARLETTE"),
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
http_application._is_instrumented_by_opentelemetry = True # type: ignore[attr-defined] # noqa: SLF001
|
|
116
|
+
return http_application
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def get_config_type(cls) -> type[opentelemetry_instrument.OpentelemetryConfig]:
|
|
120
|
+
return opentelemetry_instrument.OpentelemetryConfig
|
|
121
|
+
|
|
122
|
+
|
|
54
123
|
@FastMcpBootstrapper.use_instrument()
|
|
55
124
|
class FastMcpLoggingInstrument(LoggingInstrument):
|
|
56
125
|
def bootstrap_after(self, application: FastMCP[typing.Any]) -> FastMCP[typing.Any]: # type: ignore[override]
|
|
@@ -15,11 +15,12 @@ from litestar.openapi.plugins import SwaggerRenderPlugin
|
|
|
15
15
|
from litestar.types.asgi_types import ASGIApp, Scope
|
|
16
16
|
from litestar_offline_docs import generate_static_files_config
|
|
17
17
|
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
|
18
|
-
from opentelemetry.util.http import get_excluded_urls
|
|
18
|
+
from opentelemetry.util.http import ExcludeList, get_excluded_urls
|
|
19
19
|
from sentry_sdk.integrations.litestar import LitestarIntegration
|
|
20
20
|
|
|
21
21
|
from microbootstrap.bootstrappers.base import ApplicationBootstrapper
|
|
22
22
|
from microbootstrap.config.litestar import LitestarConfig
|
|
23
|
+
from microbootstrap.instruments import opentelemetry_instrument
|
|
23
24
|
from microbootstrap.instruments.cors_instrument import CorsInstrument
|
|
24
25
|
from microbootstrap.instruments.health_checks_instrument import (
|
|
25
26
|
HealthChecksInstrument,
|
|
@@ -35,7 +36,6 @@ from microbootstrap.instruments.openapi_security_schemes import (
|
|
|
35
36
|
serialize_security_schemes,
|
|
36
37
|
)
|
|
37
38
|
from microbootstrap.instruments.openapi_version_docs import SUPPORTED_HTTP_METHODS
|
|
38
|
-
from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryInstrument
|
|
39
39
|
from microbootstrap.instruments.prometheus_instrument import (
|
|
40
40
|
LitestarPrometheusConfig,
|
|
41
41
|
PrometheusInstrument,
|
|
@@ -239,12 +239,6 @@ class LitestarCorsInstrument(CorsInstrument):
|
|
|
239
239
|
LitestarBootstrapper.use_instrument()(PyroscopeInstrument)
|
|
240
240
|
|
|
241
241
|
|
|
242
|
-
def build_span_name(method: str, route: str) -> str:
|
|
243
|
-
if not route:
|
|
244
|
-
return method
|
|
245
|
-
return f"{method} {route}"
|
|
246
|
-
|
|
247
|
-
|
|
248
242
|
def build_litestar_route_details_from_scope(
|
|
249
243
|
scope: Scope,
|
|
250
244
|
) -> tuple[str, dict[str, str]]:
|
|
@@ -261,18 +255,23 @@ def build_litestar_route_details_from_scope(
|
|
|
261
255
|
method: typing.Final = str(scope.get("method", "HTTP")).strip()
|
|
262
256
|
if path_template is not None:
|
|
263
257
|
path_template_stripped: typing.Final = path_template.strip()
|
|
264
|
-
|
|
258
|
+
span_name: typing.Final = opentelemetry_instrument.build_span_name(method, path_template_stripped)
|
|
259
|
+
return span_name, {"http.route": path_template_stripped}
|
|
265
260
|
|
|
266
261
|
path: typing.Final = scope.get("path")
|
|
267
262
|
if path is not None:
|
|
268
263
|
path_stripped: typing.Final = path.strip()
|
|
269
|
-
return build_span_name(method, path_stripped), {"http.route": path_stripped}
|
|
264
|
+
return opentelemetry_instrument.build_span_name(method, path_stripped), {"http.route": path_stripped}
|
|
270
265
|
return method, {}
|
|
271
266
|
|
|
272
267
|
|
|
273
268
|
class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware):
|
|
274
|
-
def __init__(self, config: OpenTelemetryConfig) -> None:
|
|
269
|
+
def __init__(self, config: OpenTelemetryConfig, exclude_urls: typing.Sequence[str] = ()) -> None:
|
|
275
270
|
self.config = config
|
|
271
|
+
self.excluded_urls = opentelemetry_instrument.CombinedExcludeList(
|
|
272
|
+
ExcludeList(exclude_urls),
|
|
273
|
+
get_excluded_urls(self.config.exclude_urls_env_key),
|
|
274
|
+
)
|
|
276
275
|
|
|
277
276
|
def create_open_telemetry_middleware(self, app: ASGIApp) -> OpenTelemetryMiddleware:
|
|
278
277
|
return OpenTelemetryMiddleware(
|
|
@@ -280,7 +279,7 @@ class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware):
|
|
|
280
279
|
client_request_hook=self.config.client_request_hook_handler,
|
|
281
280
|
client_response_hook=self.config.client_response_hook_handler,
|
|
282
281
|
default_span_details=build_litestar_route_details_from_scope,
|
|
283
|
-
excluded_urls=
|
|
282
|
+
excluded_urls=self.excluded_urls,
|
|
284
283
|
meter=self.config.meter,
|
|
285
284
|
meter_provider=self.config.meter_provider,
|
|
286
285
|
server_request_hook=self.config.server_request_hook_handler,
|
|
@@ -292,7 +291,7 @@ class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware):
|
|
|
292
291
|
|
|
293
292
|
|
|
294
293
|
@LitestarBootstrapper.use_instrument()
|
|
295
|
-
class LitestarOpentelemetryInstrument(OpentelemetryInstrument):
|
|
294
|
+
class LitestarOpentelemetryInstrument(opentelemetry_instrument.OpentelemetryInstrument):
|
|
296
295
|
def bootstrap_before(self) -> dict[str, typing.Any]:
|
|
297
296
|
return {
|
|
298
297
|
"middleware": [
|
|
@@ -300,7 +299,8 @@ class LitestarOpentelemetryInstrument(OpentelemetryInstrument):
|
|
|
300
299
|
LitestarOpentelemetryConfig(
|
|
301
300
|
tracer_provider=self.tracer_provider,
|
|
302
301
|
middleware_class=LitestarOpenTelemetryInstrumentationMiddleware, # type: ignore[arg-type]
|
|
303
|
-
)
|
|
302
|
+
),
|
|
303
|
+
exclude_urls=self.define_exclude_urls(),
|
|
304
304
|
)
|
|
305
305
|
]
|
|
306
306
|
}
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/opentelemetry_instrument.py
RENAMED
|
@@ -19,6 +19,7 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExport
|
|
|
19
19
|
from opentelemetry.semconv.resource import ResourceAttributes
|
|
20
20
|
from opentelemetry.trace import SpanKind, format_span_id, get_current_span, set_tracer_provider
|
|
21
21
|
from opentelemetry.util._importlib_metadata import entry_points
|
|
22
|
+
from opentelemetry.util.http import ExcludeList
|
|
22
23
|
|
|
23
24
|
from microbootstrap.instruments.base import BaseInstrumentConfig, Instrument
|
|
24
25
|
from microbootstrap.instruments.sentry_instrument import snapshot_sentry_opentelemetry_baggage
|
|
@@ -70,6 +71,21 @@ def opentelemetry_baggage_scope(
|
|
|
70
71
|
context.detach(token)
|
|
71
72
|
|
|
72
73
|
|
|
74
|
+
def build_span_name(method: str, route: str) -> str:
|
|
75
|
+
if not route:
|
|
76
|
+
return method
|
|
77
|
+
return f"{method} {route}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CombinedExcludeList(ExcludeList):
|
|
81
|
+
def __init__(self, *exclude_lists: ExcludeList) -> None:
|
|
82
|
+
super().__init__([])
|
|
83
|
+
self.exclude_lists = exclude_lists
|
|
84
|
+
|
|
85
|
+
def url_disabled(self, url: str) -> bool:
|
|
86
|
+
return any(one_exclude_list.url_disabled(url) for one_exclude_list in self.exclude_lists)
|
|
87
|
+
|
|
88
|
+
|
|
73
89
|
@dataclasses.dataclass()
|
|
74
90
|
class OpenTelemetryInstrumentor:
|
|
75
91
|
instrumentor: BaseInstrumentor
|
|
@@ -167,6 +183,16 @@ class BaseOpentelemetryInstrument(Instrument[OpentelemetryConfigT]):
|
|
|
167
183
|
LOGGER_OBJ.debug("Instrumenting failed", entry_point_name=entry_point.name)
|
|
168
184
|
raise
|
|
169
185
|
|
|
186
|
+
def define_exclude_urls(self) -> list[str]:
|
|
187
|
+
exclude_urls: typing.Final = [*self.instrument_config.opentelemetry_exclude_urls]
|
|
188
|
+
if (
|
|
189
|
+
not self.instrument_config.opentelemetry_generate_health_check_spans
|
|
190
|
+
and self.instrument_config.health_checks_path
|
|
191
|
+
and self.instrument_config.health_checks_path not in exclude_urls
|
|
192
|
+
):
|
|
193
|
+
exclude_urls.append(self.instrument_config.health_checks_path)
|
|
194
|
+
return exclude_urls
|
|
195
|
+
|
|
170
196
|
def is_ready(self) -> bool:
|
|
171
197
|
return (
|
|
172
198
|
bool(self.instrument_config.opentelemetry_endpoint)
|
|
@@ -222,16 +248,6 @@ class BaseOpentelemetryInstrument(Instrument[OpentelemetryConfigT]):
|
|
|
222
248
|
|
|
223
249
|
|
|
224
250
|
class OpentelemetryInstrument(BaseOpentelemetryInstrument[OpentelemetryConfig]):
|
|
225
|
-
def define_exclude_urls(self) -> list[str]:
|
|
226
|
-
exclude_urls: typing.Final = [*self.instrument_config.opentelemetry_exclude_urls]
|
|
227
|
-
if (
|
|
228
|
-
not self.instrument_config.opentelemetry_generate_health_check_spans
|
|
229
|
-
and self.instrument_config.health_checks_path
|
|
230
|
-
and self.instrument_config.health_checks_path not in exclude_urls
|
|
231
|
-
):
|
|
232
|
-
exclude_urls.append(self.instrument_config.health_checks_path)
|
|
233
|
-
return exclude_urls
|
|
234
|
-
|
|
235
251
|
@classmethod
|
|
236
252
|
def get_config_type(cls) -> type[OpentelemetryConfig]:
|
|
237
253
|
return OpentelemetryConfig
|
|
@@ -58,7 +58,7 @@ dependencies = [
|
|
|
58
58
|
"opentelemetry-instrumentation-asgi>=0.46b0",
|
|
59
59
|
"orjson>=3.10.18",
|
|
60
60
|
]
|
|
61
|
-
version = "1.
|
|
61
|
+
version = "1.7.0"
|
|
62
62
|
|
|
63
63
|
[[project.authors]]
|
|
64
64
|
name = "community-of-python"
|
|
@@ -90,6 +90,7 @@ dev = [
|
|
|
90
90
|
"anyio>=4.8.0",
|
|
91
91
|
"httpx>=0.28.1",
|
|
92
92
|
"mypy>=1.14.1",
|
|
93
|
+
"opentelemetry-instrumentation-starlette>=0.54b1",
|
|
93
94
|
"pre-commit>=4.0.1",
|
|
94
95
|
"pytest>=8.3.4",
|
|
95
96
|
"pytest-cov>=6.0.0",
|
|
@@ -58,7 +58,7 @@ dependencies = [
|
|
|
58
58
|
"opentelemetry-instrumentation-asgi>=0.46b0",
|
|
59
59
|
"orjson>=3.10.18",
|
|
60
60
|
]
|
|
61
|
-
version = "1.
|
|
61
|
+
version = "1.7.0"
|
|
62
62
|
authors = [{ name = "community-of-python" }]
|
|
63
63
|
|
|
64
64
|
[project.optional-dependencies]
|
|
@@ -82,6 +82,7 @@ dev = [
|
|
|
82
82
|
"anyio>=4.8.0",
|
|
83
83
|
"httpx>=0.28.1",
|
|
84
84
|
"mypy>=1.14.1",
|
|
85
|
+
"opentelemetry-instrumentation-starlette>=0.54b1",
|
|
85
86
|
"pre-commit>=4.0.1",
|
|
86
87
|
"pytest>=8.3.4",
|
|
87
88
|
"pytest-cov>=6.0.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/health_checks_instrument.py
RENAMED
|
File without changes
|
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/logging_instrument.py
RENAMED
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/openapi_security_schemes.py
RENAMED
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/openapi_version_docs.py
RENAMED
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/prometheus_instrument.py
RENAMED
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/pyroscope_instrument.py
RENAMED
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/sentry_instrument.py
RENAMED
|
File without changes
|
{microbootstrap-1.6.0 → microbootstrap-1.7.0}/microbootstrap/instruments/swagger_instrument.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|