Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lite_bootstrap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
FastAPIHealthChecksInstrument,
FastAPILoggingInstrument,
FastAPIOpenTelemetryInstrument,
FastAPIPrometheusInstrument,
FastAPISentryInstrument,
)
from lite_bootstrap.bootstrappers.free_bootstrapper import FreeBootstrapper
Expand All @@ -11,6 +12,7 @@
LitestarHealthChecksInstrument,
LitestarLoggingInstrument,
LitestarOpenTelemetryInstrument,
LitestarPrometheusInstrument,
LitestarSentryInstrument,
)
from lite_bootstrap.instruments.healthchecks_instrument import HealthChecksInstrument
Expand All @@ -25,13 +27,15 @@
"FastAPIHealthChecksInstrument",
"FastAPILoggingInstrument",
"FastAPIOpenTelemetryInstrument",
"FastAPIPrometheusInstrument",
"FastAPISentryInstrument",
"FreeBootstrapper",
"HealthChecksInstrument",
"LitestarBootstrapper",
"LitestarHealthChecksInstrument",
"LitestarLoggingInstrument",
"LitestarOpenTelemetryInstrument",
"LitestarPrometheusInstrument",
"LitestarSentryInstrument",
"LoggingInstrument",
"OpenTelemetryInstrument",
Expand Down
25 changes: 25 additions & 0 deletions lite_bootstrap/bootstrappers/fastapi_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import dataclasses
import typing

from prometheus_fastapi_instrumentator import Instrumentator

from lite_bootstrap.bootstrappers.base import BaseBootstrapper
from lite_bootstrap.instruments.healthchecks_instrument import HealthChecksInstrument, HealthCheckTypedDict
from lite_bootstrap.instruments.logging_instrument import LoggingInstrument
from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrument
from lite_bootstrap.instruments.prometheus_instrument import PrometheusInstrument
from lite_bootstrap.instruments.sentry_instrument import SentryInstrument
from lite_bootstrap.service_config import ServiceConfig

Expand Down Expand Up @@ -65,6 +68,27 @@ def teardown(self, application: fastapi.FastAPI | None = None) -> None:
class FastAPISentryInstrument(SentryInstrument): ...


@dataclasses.dataclass(kw_only=True, frozen=True)
class FastAPIPrometheusInstrument(PrometheusInstrument):
metrics_path: str = "/metrics"
metrics_include_in_schema: bool = False
instrumentator_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
instrument_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
expose_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)

def bootstrap(self, _: ServiceConfig, application: fastapi.FastAPI | None = None) -> None:
if application:
Instrumentator(**self.instrumentator_params).instrument(
application,
**self.instrument_params,
).expose(
application,
endpoint=self.metrics_path,
include_in_schema=self.metrics_include_in_schema,
**self.expose_params,
)


@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class FastAPIBootstrapper(BaseBootstrapper[fastapi.FastAPI, fastapi.FastAPI]):
bootstrap_object: fastapi.FastAPI
Expand All @@ -73,6 +97,7 @@ class FastAPIBootstrapper(BaseBootstrapper[fastapi.FastAPI, fastapi.FastAPI]):
| FastAPISentryInstrument
| FastAPIHealthChecksInstrument
| FastAPILoggingInstrument
| FastAPIPrometheusInstrument
]
service_config: ServiceConfig

Expand Down
24 changes: 24 additions & 0 deletions lite_bootstrap/bootstrappers/litestar_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import dataclasses
import typing

from litestar.plugins.prometheus import PrometheusConfig, PrometheusController

from lite_bootstrap.bootstrappers.base import BaseBootstrapper
from lite_bootstrap.instruments.healthchecks_instrument import HealthChecksInstrument, HealthCheckTypedDict
from lite_bootstrap.instruments.logging_instrument import LoggingInstrument
from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrument
from lite_bootstrap.instruments.prometheus_instrument import PrometheusInstrument
from lite_bootstrap.instruments.sentry_instrument import SentryInstrument
from lite_bootstrap.service_config import ServiceConfig

Expand Down Expand Up @@ -63,6 +66,26 @@ def bootstrap(self, service_config: ServiceConfig, app_config: AppConfig | None
class LitestarSentryInstrument(SentryInstrument): ...


@dataclasses.dataclass(kw_only=True, frozen=True)
class LitestarPrometheusInstrument(PrometheusInstrument):
additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)

def bootstrap(self, service_config: ServiceConfig, app_config: AppConfig | None = None) -> None:
class LitestarPrometheusController(PrometheusController):
path = self.metrics_path
include_in_schema = self.metrics_include_in_schema
openmetrics_format = True

litestar_prometheus_config = PrometheusConfig(
app_name=service_config.service_name,
**self.additional_params,
)

if app_config:
app_config.route_handlers.append(LitestarPrometheusController)
app_config.middleware.append(litestar_prometheus_config.middleware)


@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class LitestarBootstrapper(BaseBootstrapper[AppConfig, litestar.Litestar]):
bootstrap_object: AppConfig
Expand All @@ -71,6 +94,7 @@ class LitestarBootstrapper(BaseBootstrapper[AppConfig, litestar.Litestar]):
| LitestarSentryInstrument
| LitestarHealthChecksInstrument
| LitestarLoggingInstrument
| LitestarPrometheusInstrument
]
service_config: ServiceConfig

Expand Down
22 changes: 22 additions & 0 deletions lite_bootstrap/instruments/prometheus_instrument.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import dataclasses
import re
import typing

from lite_bootstrap.instruments.base import BaseInstrument
from lite_bootstrap.service_config import ServiceConfig


VALID_PATH_PATTERN: typing.Final = re.compile(r"^(/[a-zA-Z0-9_-]+)+/?$")


def _is_valid_path(maybe_path: str) -> bool:
return bool(re.fullmatch(VALID_PATH_PATTERN, maybe_path))


@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class PrometheusInstrument(BaseInstrument):
metrics_path: str = "/metrics"
metrics_include_in_schema: bool = False

def is_ready(self, _: ServiceConfig) -> bool:
return bool(self.metrics_path) and _is_valid_path(self.metrics_path)
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ fastapi = [
fastapi-otl = [
"opentelemetry-instrumentation-fastapi",
]
fastapi-metrics = [
"prometheus-fastapi-instrumentator>=6.1",
]
fastapi-all = [
"lite-bootstrap[sentry,otl,logging,fastapi,fastapi-otl]"
"lite-bootstrap[sentry,otl,logging,fastapi,fastapi-otl,fastapi-metrics]"
]
litestar = [
"litestar>=2.9",
Expand Down
11 changes: 11 additions & 0 deletions tests/test_fastapi_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
FastAPIHealthChecksInstrument,
FastAPILoggingInstrument,
FastAPIOpenTelemetryInstrument,
FastAPIPrometheusInstrument,
FastAPISentryInstrument,
ServiceConfig,
)
Expand All @@ -35,6 +36,7 @@ def test_fastapi_bootstrap(fastapi_app: FastAPI, service_config: ServiceConfig)
path="/health/",
),
FastAPILoggingInstrument(logging_buffer_capacity=0),
FastAPIPrometheusInstrument(),
],
)
bootstrapper.bootstrap()
Expand All @@ -46,3 +48,12 @@ def test_fastapi_bootstrap(fastapi_app: FastAPI, service_config: ServiceConfig)
assert response.json() == {"health_status": True, "service_name": "microservice", "service_version": "2.0.0"}
finally:
bootstrapper.teardown()


def test_fastapi_prometheus_instrument(fastapi_app: FastAPI, service_config: ServiceConfig) -> None:
prometheus_instrument = FastAPIPrometheusInstrument(metrics_path="/custom-metrics-path")
prometheus_instrument.bootstrap(service_config, fastapi_app)

response = TestClient(fastapi_app).get(prometheus_instrument.metrics_path)
assert response.status_code == status.HTTP_200_OK
assert response.text
18 changes: 18 additions & 0 deletions tests/test_litestar_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
LitestarHealthChecksInstrument,
LitestarLoggingInstrument,
LitestarOpenTelemetryInstrument,
LitestarPrometheusInstrument,
LitestarSentryInstrument,
ServiceConfig,
)
Expand Down Expand Up @@ -36,6 +37,7 @@ def test_litestar_bootstrap(service_config: ServiceConfig) -> None:
path="/health/",
),
LitestarLoggingInstrument(logging_buffer_capacity=0),
LitestarPrometheusInstrument(),
],
)
application = bootstrapper.bootstrap()
Expand All @@ -52,3 +54,19 @@ def test_litestar_bootstrap(service_config: ServiceConfig) -> None:
}
finally:
bootstrapper.teardown()


def test_litestar_prometheus_bootstrap(service_config: ServiceConfig) -> None:
app_config = AppConfig()
prometheus_instrument = LitestarPrometheusInstrument(metrics_path="/custom-metrics-path")
bootstrapper = LitestarBootstrapper(
bootstrap_object=app_config,
service_config=service_config,
instruments=[prometheus_instrument],
)
application = bootstrapper.bootstrap()

with TestClient(app=application) as test_client:
response = test_client.get(prometheus_instrument.metrics_path)
assert response.status_code == status_codes.HTTP_200_OK
assert response.text