diff --git a/lite_bootstrap/__init__.py b/lite_bootstrap/__init__.py index 26476e9..8dc9383 100644 --- a/lite_bootstrap/__init__.py +++ b/lite_bootstrap/__init__.py @@ -1,44 +1,13 @@ -from lite_bootstrap.bootstrappers.fastapi_bootstrapper import ( - FastAPIBootstrapper, - FastAPIHealthChecksInstrument, - FastAPILoggingInstrument, - FastAPIOpenTelemetryInstrument, - FastAPIPrometheusInstrument, - FastAPISentryInstrument, -) -from lite_bootstrap.bootstrappers.free_bootstrapper import FreeBootstrapper -from lite_bootstrap.bootstrappers.litestar_bootstrapper import ( - LitestarBootstrapper, - LitestarHealthChecksInstrument, - LitestarLoggingInstrument, - LitestarOpenTelemetryInstrument, - LitestarPrometheusInstrument, - LitestarSentryInstrument, -) -from lite_bootstrap.instruments.healthchecks_instrument import HealthChecksInstrument -from lite_bootstrap.instruments.logging_instrument import LoggingInstrument -from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrument -from lite_bootstrap.instruments.sentry_instrument import SentryInstrument -from lite_bootstrap.service_config import ServiceConfig +from lite_bootstrap.bootstrappers.fastapi_bootstrapper import FastAPIBootstrapper, FastAPIConfig +from lite_bootstrap.bootstrappers.free_bootstrapper import FreeBootstrapper, FreeBootstrapperConfig +from lite_bootstrap.bootstrappers.litestar_bootstrapper import LitestarBootstrapper, LitestarConfig __all__ = [ "FastAPIBootstrapper", - "FastAPIHealthChecksInstrument", - "FastAPILoggingInstrument", - "FastAPIOpenTelemetryInstrument", - "FastAPIPrometheusInstrument", - "FastAPISentryInstrument", + "FastAPIConfig", "FreeBootstrapper", - "HealthChecksInstrument", + "FreeBootstrapperConfig", "LitestarBootstrapper", - "LitestarHealthChecksInstrument", - "LitestarLoggingInstrument", - "LitestarOpenTelemetryInstrument", - "LitestarPrometheusInstrument", - "LitestarSentryInstrument", - "LoggingInstrument", - "OpenTelemetryInstrument", - "SentryInstrument", - "ServiceConfig", + "LitestarConfig", ] diff --git a/lite_bootstrap/bootstrappers/base.py b/lite_bootstrap/bootstrappers/base.py index 727c56d..9d0121b 100644 --- a/lite_bootstrap/bootstrappers/base.py +++ b/lite_bootstrap/bootstrappers/base.py @@ -1,26 +1,34 @@ import abc import typing -from lite_bootstrap.instruments.base import BaseInstrument -from lite_bootstrap.service_config import ServiceConfig -from lite_bootstrap.types import ApplicationT, BootstrapObjectT +from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument +from lite_bootstrap.types import ApplicationT -class BaseBootstrapper(abc.ABC, typing.Generic[BootstrapObjectT, ApplicationT]): - bootstrap_object: BootstrapObjectT - instruments: typing.Sequence[BaseInstrument] - service_config: ServiceConfig +InstrumentT = typing.TypeVar("InstrumentT", bound=BaseInstrument) + + +class BaseBootstrapper(abc.ABC, typing.Generic[ApplicationT]): + instruments_types: typing.ClassVar[list[type[BaseInstrument]]] + instruments: list[BaseInstrument] + bootstrap_config: BaseConfig + + def __init__(self, bootstrap_config: BaseConfig) -> None: + self.bootstrap_config = bootstrap_config + self.instruments = [] + for instrument_type in self.instruments_types: + instrument = instrument_type(bootstrap_config=bootstrap_config) + if instrument.is_ready(): + self.instruments.append(instrument) @abc.abstractmethod def _prepare_application(self) -> ApplicationT: ... def bootstrap(self) -> ApplicationT: for one_instrument in self.instruments: - if one_instrument.is_ready(self.service_config): - one_instrument.bootstrap(self.service_config, self.bootstrap_object) + one_instrument.bootstrap() return self._prepare_application() def teardown(self) -> None: for one_instrument in self.instruments: - if one_instrument.is_ready(self.service_config): - one_instrument.teardown(self.bootstrap_object) + one_instrument.teardown() diff --git a/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py b/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py index 213a072..534343e 100644 --- a/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py @@ -5,12 +5,15 @@ 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 +from lite_bootstrap.instruments.healthchecks_instrument import ( + HealthChecksConfig, + HealthChecksInstrument, + HealthCheckTypedDict, +) +from lite_bootstrap.instruments.logging_instrument import LoggingConfig, LoggingInstrument +from lite_bootstrap.instruments.opentelemetry_instrument import OpentelemetryConfig, OpenTelemetryInstrument +from lite_bootstrap.instruments.prometheus_instrument import PrometheusConfig, PrometheusInstrument +from lite_bootstrap.instruments.sentry_instrument import SentryConfig, SentryInstrument with contextlib.suppress(ImportError): @@ -19,87 +22,91 @@ from opentelemetry.trace import get_tracer_provider +@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) +class FastAPIConfig(HealthChecksConfig, LoggingConfig, OpentelemetryConfig, PrometheusConfig, SentryConfig): + application: fastapi.FastAPI = dataclasses.field(default_factory=fastapi.FastAPI) + opentelemetry_excluded_urls: list[str] = dataclasses.field(default_factory=list) + prometheus_instrumentator_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) + prometheus_instrument_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) + prometheus_expose_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) + + @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) class FastAPIHealthChecksInstrument(HealthChecksInstrument): - enabled: bool = True - path: str = "/health/" - include_in_schema: bool = False + bootstrap_config: FastAPIConfig - def build_fastapi_health_check_router(self, service_config: ServiceConfig) -> fastapi.APIRouter: + def build_fastapi_health_check_router(self) -> fastapi.APIRouter: fastapi_router = fastapi.APIRouter( tags=["probes"], - include_in_schema=self.include_in_schema, + include_in_schema=self.bootstrap_config.health_checks_include_in_schema, ) - @fastapi_router.get(self.path) + @fastapi_router.get(self.bootstrap_config.health_checks_path) async def health_check_handler() -> HealthCheckTypedDict: - return self.render_health_check_data(service_config) + return self.render_health_check_data() return fastapi_router - def bootstrap(self, service_config: ServiceConfig, application: fastapi.FastAPI | None = None) -> None: - if application: - application.include_router(self.build_fastapi_health_check_router(service_config)) + def bootstrap(self) -> None: + self.bootstrap_config.application.include_router(self.build_fastapi_health_check_router()) @dataclasses.dataclass(kw_only=True, frozen=True) -class FastAPILoggingInstrument(LoggingInstrument): ... +class FastAPILoggingInstrument(LoggingInstrument): + bootstrap_config: FastAPIConfig @dataclasses.dataclass(kw_only=True, frozen=True) class FastAPIOpenTelemetryInstrument(OpenTelemetryInstrument): - excluded_urls: list[str] = dataclasses.field(default_factory=list) + bootstrap_config: FastAPIConfig - def bootstrap(self, service_config: ServiceConfig, application: fastapi.FastAPI | None = None) -> None: - super().bootstrap(service_config, application) + def bootstrap(self) -> None: + super().bootstrap() FastAPIInstrumentor.instrument_app( - app=application, + app=self.bootstrap_config.application, tracer_provider=get_tracer_provider(), - excluded_urls=",".join(self.excluded_urls), + excluded_urls=",".join(self.bootstrap_config.opentelemetry_excluded_urls), ) - def teardown(self, application: fastapi.FastAPI | None = None) -> None: - if application: - FastAPIInstrumentor.uninstrument_app(application) + def teardown(self) -> None: + FastAPIInstrumentor.uninstrument_app(self.bootstrap_config.application) super().teardown() @dataclasses.dataclass(kw_only=True, frozen=True) -class FastAPISentryInstrument(SentryInstrument): ... +class FastAPISentryInstrument(SentryInstrument): + bootstrap_config: FastAPIConfig @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, - ) + bootstrap_config: FastAPIConfig + + def bootstrap(self) -> None: + Instrumentator(**self.bootstrap_config.prometheus_instrument_params).instrument( + self.bootstrap_config.application, + **self.bootstrap_config.prometheus_instrument_params, + ).expose( + self.bootstrap_config.application, + endpoint=self.bootstrap_config.prometheus_metrics_path, + include_in_schema=self.bootstrap_config.prometheus_metrics_include_in_schema, + **self.bootstrap_config.prometheus_expose_params, + ) -@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) -class FastAPIBootstrapper(BaseBootstrapper[fastapi.FastAPI, fastapi.FastAPI]): - bootstrap_object: fastapi.FastAPI - instruments: typing.Sequence[ - FastAPIOpenTelemetryInstrument - | FastAPISentryInstrument - | FastAPIHealthChecksInstrument - | FastAPILoggingInstrument - | FastAPIPrometheusInstrument +class FastAPIBootstrapper(BaseBootstrapper[fastapi.FastAPI]): + instruments_types: typing.ClassVar = [ + FastAPIOpenTelemetryInstrument, + FastAPISentryInstrument, + FastAPIHealthChecksInstrument, + FastAPILoggingInstrument, + FastAPIPrometheusInstrument, ] - service_config: ServiceConfig + bootstrap_config: FastAPIConfig + __slots__ = "bootstrap_config", "instruments" + + def __init__(self, bootstrap_config: FastAPIConfig) -> None: + super().__init__(bootstrap_config) def _prepare_application(self) -> fastapi.FastAPI: - return self.bootstrap_object + return self.bootstrap_config.application diff --git a/lite_bootstrap/bootstrappers/free_bootstrapper.py b/lite_bootstrap/bootstrappers/free_bootstrapper.py index abbbd72..13f9bc2 100644 --- a/lite_bootstrap/bootstrappers/free_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/free_bootstrapper.py @@ -2,17 +2,26 @@ import typing from lite_bootstrap.bootstrappers.base import BaseBootstrapper -from lite_bootstrap.instruments.logging_instrument import LoggingInstrument -from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrument -from lite_bootstrap.instruments.sentry_instrument import SentryInstrument -from lite_bootstrap.service_config import ServiceConfig +from lite_bootstrap.instruments.logging_instrument import LoggingConfig, LoggingInstrument +from lite_bootstrap.instruments.opentelemetry_instrument import OpentelemetryConfig, OpenTelemetryInstrument +from lite_bootstrap.instruments.sentry_instrument import SentryConfig, SentryInstrument @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) -class FreeBootstrapper(BaseBootstrapper[None, None]): - bootstrap_object: None = None - instruments: typing.Sequence[OpenTelemetryInstrument | SentryInstrument | LoggingInstrument] - service_config: ServiceConfig +class FreeBootstrapperConfig(LoggingConfig, OpentelemetryConfig, SentryConfig): ... + + +class FreeBootstrapper(BaseBootstrapper[None]): + instruments_types: typing.ClassVar = [ + OpenTelemetryInstrument, + SentryInstrument, + LoggingInstrument, + ] + bootstrap_config: FreeBootstrapperConfig + __slots__ = "bootstrap_config", "instruments" + + def __init__(self, bootstrap_config: FreeBootstrapperConfig) -> None: + super().__init__(bootstrap_config) def _prepare_application(self) -> None: - return self.bootstrap_object + return None diff --git a/lite_bootstrap/bootstrappers/litestar_bootstrapper.py b/lite_bootstrap/bootstrappers/litestar_bootstrapper.py index 660f1e4..8514c15 100644 --- a/lite_bootstrap/bootstrappers/litestar_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/litestar_bootstrapper.py @@ -5,12 +5,20 @@ 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 +from lite_bootstrap.instruments.healthchecks_instrument import ( + HealthChecksConfig, + HealthChecksInstrument, + HealthCheckTypedDict, +) +from lite_bootstrap.instruments.logging_instrument import LoggingConfig, LoggingInstrument +from lite_bootstrap.instruments.opentelemetry_instrument import OpentelemetryConfig, OpenTelemetryInstrument +from lite_bootstrap.instruments.prometheus_instrument import ( + PrometheusConfig as PrometheusBootstrapperConfig, +) +from lite_bootstrap.instruments.prometheus_instrument import ( + PrometheusInstrument, +) +from lite_bootstrap.instruments.sentry_instrument import SentryConfig, SentryInstrument with contextlib.suppress(ImportError): @@ -20,83 +28,91 @@ from opentelemetry.trace import get_tracer_provider +@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) +class LitestarConfig( + HealthChecksConfig, LoggingConfig, OpentelemetryConfig, PrometheusBootstrapperConfig, SentryConfig +): + application_config: AppConfig = dataclasses.field(default_factory=AppConfig) + opentelemetry_excluded_urls: list[str] = dataclasses.field(default_factory=list) + prometheus_additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) + + @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) class LitestarHealthChecksInstrument(HealthChecksInstrument): - enabled: bool = True - path: str = "/health/" - include_in_schema: bool = False + bootstrap_config: LitestarConfig - def build_litestar_health_check_router(self, service_config: ServiceConfig) -> litestar.Router: + def build_litestar_health_check_router(self) -> litestar.Router: @litestar.get(media_type=litestar.MediaType.JSON) async def health_check_handler() -> HealthCheckTypedDict: - return self.render_health_check_data(service_config) + return self.render_health_check_data() return litestar.Router( - path=self.path, + path=self.bootstrap_config.health_checks_path, route_handlers=[health_check_handler], tags=["probes"], - include_in_schema=self.include_in_schema, + include_in_schema=self.bootstrap_config.health_checks_include_in_schema, ) - def bootstrap(self, service_config: ServiceConfig, app_config: AppConfig | None = None) -> None: - if app_config: - app_config.route_handlers.append(self.build_litestar_health_check_router(service_config)) + def bootstrap(self) -> None: + self.bootstrap_config.application_config.route_handlers.append(self.build_litestar_health_check_router()) @dataclasses.dataclass(kw_only=True, frozen=True) -class LitestarLoggingInstrument(LoggingInstrument): ... +class LitestarLoggingInstrument(LoggingInstrument): + bootstrap_config: LitestarConfig @dataclasses.dataclass(kw_only=True, frozen=True) class LitestarOpenTelemetryInstrument(OpenTelemetryInstrument): - excluded_urls: list[str] = dataclasses.field(default_factory=list) - - def bootstrap(self, service_config: ServiceConfig, app_config: AppConfig | None = None) -> None: - super().bootstrap(service_config, app_config) - if app_config: - app_config.middleware.append( - OpenTelemetryConfig( - tracer_provider=get_tracer_provider(), - exclude=self.excluded_urls, - ).middleware, - ) + bootstrap_config: LitestarConfig + + def bootstrap(self) -> None: + super().bootstrap() + self.bootstrap_config.application_config.middleware.append( + OpenTelemetryConfig( + tracer_provider=get_tracer_provider(), + exclude=self.bootstrap_config.opentelemetry_excluded_urls, + ).middleware, + ) @dataclasses.dataclass(kw_only=True, frozen=True) -class LitestarSentryInstrument(SentryInstrument): ... +class LitestarSentryInstrument(SentryInstrument): + bootstrap_config: LitestarConfig @dataclasses.dataclass(kw_only=True, frozen=True) class LitestarPrometheusInstrument(PrometheusInstrument): - additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) + bootstrap_config: LitestarConfig - def bootstrap(self, service_config: ServiceConfig, app_config: AppConfig | None = None) -> None: + def bootstrap(self) -> None: class LitestarPrometheusController(PrometheusController): - path = self.metrics_path - include_in_schema = self.metrics_include_in_schema + path = self.bootstrap_config.prometheus_metrics_path + include_in_schema = self.bootstrap_config.prometheus_metrics_include_in_schema openmetrics_format = True litestar_prometheus_config = PrometheusConfig( - app_name=service_config.service_name, - **self.additional_params, + app_name=self.bootstrap_config.service_name, + **self.bootstrap_config.prometheus_additional_params, ) - if app_config: - app_config.route_handlers.append(LitestarPrometheusController) - app_config.middleware.append(litestar_prometheus_config.middleware) + self.bootstrap_config.application_config.route_handlers.append(LitestarPrometheusController) + self.bootstrap_config.application_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 - instruments: typing.Sequence[ - LitestarOpenTelemetryInstrument - | LitestarSentryInstrument - | LitestarHealthChecksInstrument - | LitestarLoggingInstrument - | LitestarPrometheusInstrument +class LitestarBootstrapper(BaseBootstrapper[litestar.Litestar]): + instruments_types: typing.ClassVar = [ + LitestarOpenTelemetryInstrument, + LitestarSentryInstrument, + LitestarHealthChecksInstrument, + LitestarLoggingInstrument, + LitestarPrometheusInstrument, ] - service_config: ServiceConfig + bootstrap_config: LitestarConfig + __slots__ = "bootstrap_config", "instruments" + + def __init__(self, bootstrap_config: LitestarConfig) -> None: + super().__init__(bootstrap_config) def _prepare_application(self) -> litestar.Litestar: - return litestar.Litestar.from_config(self.bootstrap_object) + return litestar.Litestar.from_config(self.bootstrap_config.application_config) diff --git a/lite_bootstrap/instruments/base.py b/lite_bootstrap/instruments/base.py index 0d5cbf9..cbdddfb 100644 --- a/lite_bootstrap/instruments/base.py +++ b/lite_bootstrap/instruments/base.py @@ -1,13 +1,22 @@ import abc +import dataclasses -from lite_bootstrap.service_config import ServiceConfig -from lite_bootstrap.types import BootstrapObjectT +@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) +class BaseConfig: + service_name: str = "micro-service" + service_version: str = "1.0.0" + service_environment: str | None = None + service_debug: bool = True + +@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) class BaseInstrument(abc.ABC): - def bootstrap(self, service_config: ServiceConfig, bootstrap_object: BootstrapObjectT | None = None) -> None: ... # noqa: B027 + bootstrap_config: BaseConfig + + def bootstrap(self) -> None: ... # noqa: B027 - def teardown(self, bootstrap_object: BootstrapObjectT | None = None) -> None: ... # noqa: B027 + def teardown(self) -> None: ... # noqa: B027 @abc.abstractmethod - def is_ready(self, service_config: ServiceConfig) -> bool: ... + def is_ready(self) -> bool: ... diff --git a/lite_bootstrap/instruments/healthchecks_instrument.py b/lite_bootstrap/instruments/healthchecks_instrument.py index dee87ce..431aa72 100644 --- a/lite_bootstrap/instruments/healthchecks_instrument.py +++ b/lite_bootstrap/instruments/healthchecks_instrument.py @@ -2,8 +2,7 @@ import typing_extensions -from lite_bootstrap.instruments.base import BaseInstrument -from lite_bootstrap.service_config import ServiceConfig +from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument class HealthCheckTypedDict(typing_extensions.TypedDict, total=False): @@ -12,19 +11,23 @@ class HealthCheckTypedDict(typing_extensions.TypedDict, total=False): health_status: bool +@dataclasses.dataclass(kw_only=True, frozen=True) +class HealthChecksConfig(BaseConfig): + health_checks_enabled: bool = True + health_checks_path: str = "/health/" + health_checks_include_in_schema: bool = False + + @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) class HealthChecksInstrument(BaseInstrument): - enabled: bool = True - path: str = "/health/" - include_in_schema: bool = False + bootstrap_config: HealthChecksConfig - def is_ready(self, _: ServiceConfig) -> bool: - return self.enabled + def is_ready(self) -> bool: + return self.bootstrap_config.health_checks_enabled - @staticmethod - def render_health_check_data(service_config: ServiceConfig) -> HealthCheckTypedDict: + def render_health_check_data(self) -> HealthCheckTypedDict: return { - "service_version": service_config.service_version, - "service_name": service_config.service_name, + "service_version": self.bootstrap_config.service_version, + "service_name": self.bootstrap_config.service_name, "health_status": True, } diff --git a/lite_bootstrap/instruments/logging_instrument.py b/lite_bootstrap/instruments/logging_instrument.py index c87e458..4423208 100644 --- a/lite_bootstrap/instruments/logging_instrument.py +++ b/lite_bootstrap/instruments/logging_instrument.py @@ -4,9 +4,7 @@ import logging.handlers import typing -from lite_bootstrap.instruments.base import BaseInstrument -from lite_bootstrap.service_config import ServiceConfig -from lite_bootstrap.types import BootstrapObjectT +from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument if typing.TYPE_CHECKING: @@ -96,8 +94,8 @@ def __call__(self, *args: typing.Any) -> logging.Logger: # noqa: ANN401 return logger -@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) -class LoggingInstrument(BaseInstrument): +@dataclasses.dataclass(kw_only=True, frozen=True) +class LoggingConfig(BaseConfig): logging_log_level: int = logging.INFO logging_flush_level: int = logging.ERROR logging_buffer_capacity: int = 10 @@ -106,28 +104,33 @@ class LoggingInstrument(BaseInstrument): default_factory=list, ) - def is_ready(self, service_config: ServiceConfig) -> bool: - return not service_config.service_debug - def bootstrap(self, _: ServiceConfig, __: BootstrapObjectT | None = None) -> None: - for unset_handlers_logger in self.logging_unset_handlers: +@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) +class LoggingInstrument(BaseInstrument): + bootstrap_config: LoggingConfig + + def is_ready(self) -> bool: + return not self.bootstrap_config.service_debug + + def bootstrap(self) -> None: + for unset_handlers_logger in self.bootstrap_config.logging_unset_handlers: logging.getLogger(unset_handlers_logger).handlers = [] structlog.configure( processors=[ *DEFAULT_STRUCTLOG_PROCESSORS, - *self.logging_extra_processors, + *self.bootstrap_config.logging_extra_processors, DEFAULT_STRUCTLOG_FORMATTER_PROCESSOR, ], context_class=dict, logger_factory=MemoryLoggerFactory( - logging_buffer_capacity=self.logging_buffer_capacity, - logging_flush_level=self.logging_flush_level, - logging_log_level=self.logging_log_level, + logging_buffer_capacity=self.bootstrap_config.logging_buffer_capacity, + logging_flush_level=self.bootstrap_config.logging_flush_level, + logging_log_level=self.bootstrap_config.logging_log_level, ), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) - def teardown(self, _: BootstrapObjectT | None = None) -> None: + def teardown(self) -> None: structlog.reset_defaults() diff --git a/lite_bootstrap/instruments/opentelemetry_instrument.py b/lite_bootstrap/instruments/opentelemetry_instrument.py index 853ab62..0c78391 100644 --- a/lite_bootstrap/instruments/opentelemetry_instrument.py +++ b/lite_bootstrap/instruments/opentelemetry_instrument.py @@ -4,9 +4,7 @@ from opentelemetry.trace import set_tracer_provider -from lite_bootstrap.instruments.base import BaseInstrument -from lite_bootstrap.service_config import ServiceConfig -from lite_bootstrap.types import BootstrapObjectT +from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument with contextlib.suppress(ImportError): @@ -23,25 +21,34 @@ class InstrumentorWithParams: additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) +@dataclasses.dataclass(kw_only=True, frozen=True) +class OpentelemetryConfig(BaseConfig): + opentelemetry_service_name: str | None = None + opentelemetry_container_name: str | None = None + opentelemetry_endpoint: str | None = None + opentelemetry_namespace: str | None = None + opentelemetry_insecure: bool = True + opentelemetry_instrumentors: list[InstrumentorWithParams | BaseInstrumentor] = dataclasses.field( + default_factory=list + ) + opentelemetry_span_exporter: SpanExporter | None = None + + @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) class OpenTelemetryInstrument(BaseInstrument): - container_name: str | None = None - endpoint: str | None = None - namespace: str | None = None - insecure: bool = True - instrumentors: list[InstrumentorWithParams | BaseInstrumentor] = dataclasses.field(default_factory=list) - span_exporter: SpanExporter | None = None + bootstrap_config: OpentelemetryConfig - def is_ready(self, _: ServiceConfig) -> bool: - return bool(self.endpoint) + def is_ready(self) -> bool: + return bool(self.bootstrap_config.opentelemetry_endpoint) - def bootstrap(self, service_config: ServiceConfig, _: BootstrapObjectT | None = None) -> None: + def bootstrap(self) -> None: attributes = { - resources.SERVICE_NAME: service_config.service_name, + resources.SERVICE_NAME: self.bootstrap_config.service_name + or self.bootstrap_config.opentelemetry_service_name, resources.TELEMETRY_SDK_LANGUAGE: "python", - resources.SERVICE_NAMESPACE: self.namespace, - resources.SERVICE_VERSION: service_config.service_version, - resources.CONTAINER_NAME: self.container_name, + resources.SERVICE_NAMESPACE: self.bootstrap_config.opentelemetry_namespace, + resources.SERVICE_VERSION: self.bootstrap_config.service_version, + resources.CONTAINER_NAME: self.bootstrap_config.opentelemetry_container_name, } resource: typing.Final = resources.Resource.create( attributes={k: v for k, v in attributes.items() if v}, @@ -49,14 +56,14 @@ def bootstrap(self, service_config: ServiceConfig, _: BootstrapObjectT | None = tracer_provider = TracerProvider(resource=resource) tracer_provider.add_span_processor( BatchSpanProcessor( - self.span_exporter + self.bootstrap_config.opentelemetry_span_exporter or OTLPSpanExporter( - endpoint=self.endpoint, - insecure=self.insecure, + endpoint=self.bootstrap_config.opentelemetry_endpoint, + insecure=self.bootstrap_config.opentelemetry_insecure, ), ), ) - for one_instrumentor in self.instrumentors: + for one_instrumentor in self.bootstrap_config.opentelemetry_instrumentors: if isinstance(one_instrumentor, InstrumentorWithParams): one_instrumentor.instrumentor.instrument( tracer_provider=tracer_provider, @@ -66,8 +73,8 @@ def bootstrap(self, service_config: ServiceConfig, _: BootstrapObjectT | None = one_instrumentor.instrument(tracer_provider=tracer_provider) set_tracer_provider(tracer_provider) - def teardown(self, _: BootstrapObjectT | None = None) -> None: - for one_instrumentor in self.instrumentors: + def teardown(self) -> None: + for one_instrumentor in self.bootstrap_config.opentelemetry_instrumentors: if isinstance(one_instrumentor, InstrumentorWithParams): one_instrumentor.instrumentor.uninstrument(**one_instrumentor.additional_params) else: diff --git a/lite_bootstrap/instruments/prometheus_instrument.py b/lite_bootstrap/instruments/prometheus_instrument.py index 1937e5a..90dfcc4 100644 --- a/lite_bootstrap/instruments/prometheus_instrument.py +++ b/lite_bootstrap/instruments/prometheus_instrument.py @@ -2,8 +2,7 @@ import re import typing -from lite_bootstrap.instruments.base import BaseInstrument -from lite_bootstrap.service_config import ServiceConfig +from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument VALID_PATH_PATTERN: typing.Final = re.compile(r"^(/[a-zA-Z0-9_-]+)+/?$") @@ -13,10 +12,17 @@ def _is_valid_path(maybe_path: str) -> bool: return bool(re.fullmatch(VALID_PATH_PATTERN, maybe_path)) +@dataclasses.dataclass(kw_only=True, frozen=True) +class PrometheusConfig(BaseConfig): + prometheus_metrics_path: str = "/metrics" + prometheus_metrics_include_in_schema: bool = False + + @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) class PrometheusInstrument(BaseInstrument): - metrics_path: str = "/metrics" - metrics_include_in_schema: bool = False + bootstrap_config: PrometheusConfig - def is_ready(self, _: ServiceConfig) -> bool: - return bool(self.metrics_path) and _is_valid_path(self.metrics_path) + def is_ready(self) -> bool: + return bool(self.bootstrap_config.prometheus_metrics_path) and _is_valid_path( + self.bootstrap_config.prometheus_metrics_path + ) diff --git a/lite_bootstrap/instruments/sentry_instrument.py b/lite_bootstrap/instruments/sentry_instrument.py index 5e0bd38..8dd2d6d 100644 --- a/lite_bootstrap/instruments/sentry_instrument.py +++ b/lite_bootstrap/instruments/sentry_instrument.py @@ -2,9 +2,7 @@ import dataclasses import typing -from lite_bootstrap.instruments.base import BaseInstrument -from lite_bootstrap.service_config import ServiceConfig -from lite_bootstrap.types import BootstrapObjectT +from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument with contextlib.suppress(ImportError): @@ -12,34 +10,37 @@ from sentry_sdk.integrations import Integration +@dataclasses.dataclass(kw_only=True, frozen=True) +class SentryConfig(BaseConfig): + sentry_dsn: str | None = None + sentry_traces_sample_rate: float | None = None + sentry_sample_rate: float = 1.0 + sentry_max_breadcrumbs: int = 15 + sentry_max_value_length: int = 16384 + sentry_attach_stacktrace: bool = True + sentry_integrations: list[Integration] = dataclasses.field(default_factory=list) + sentry_additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) + sentry_tags: dict[str, str] | None = None + + @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) class SentryInstrument(BaseInstrument): - dsn: str | None = None - sample_rate: float = dataclasses.field(default=1.0) - traces_sample_rate: float | None = None - max_breadcrumbs: int = 15 - max_value_length: int = 16384 - attach_stacktrace: bool = True - integrations: list[Integration] = dataclasses.field(default_factory=list) - additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) - tags: dict[str, str] | None = None - - def is_ready(self, _: ServiceConfig) -> bool: - return bool(self.dsn) - - def bootstrap(self, service_config: ServiceConfig, _: BootstrapObjectT | None = None) -> None: + bootstrap_config: SentryConfig + + def is_ready(self) -> bool: + return bool(self.bootstrap_config.sentry_dsn) + + def bootstrap(self) -> None: sentry_sdk.init( - dsn=self.dsn, - sample_rate=self.sample_rate, - traces_sample_rate=self.traces_sample_rate, - environment=service_config.service_environment, - max_breadcrumbs=self.max_breadcrumbs, - max_value_length=self.max_value_length, - attach_stacktrace=self.attach_stacktrace, - integrations=self.integrations, - **self.additional_params, + dsn=self.bootstrap_config.sentry_dsn, + sample_rate=self.bootstrap_config.sentry_sample_rate, + traces_sample_rate=self.bootstrap_config.sentry_traces_sample_rate, + environment=self.bootstrap_config.service_environment, + max_breadcrumbs=self.bootstrap_config.sentry_max_breadcrumbs, + max_value_length=self.bootstrap_config.sentry_max_value_length, + attach_stacktrace=self.bootstrap_config.sentry_attach_stacktrace, + integrations=self.bootstrap_config.sentry_integrations, + **self.bootstrap_config.sentry_additional_params, ) - tags: dict[str, str] = self.tags or {} + tags: dict[str, str] = self.bootstrap_config.sentry_tags or {} sentry_sdk.set_tags(tags) - - def teardown(self, bootstrap_object: BootstrapObjectT | None = None) -> None: ... diff --git a/lite_bootstrap/service_config.py b/lite_bootstrap/service_config.py deleted file mode 100644 index fe82f9c..0000000 --- a/lite_bootstrap/service_config.py +++ /dev/null @@ -1,9 +0,0 @@ -import dataclasses - - -@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) -class ServiceConfig: - service_name: str = "micro-service" - service_version: str = "1.0.0" - service_environment: str | None = None - service_debug: bool = True diff --git a/pyproject.toml b/pyproject.toml index 6bf33f4..71b5434 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,8 @@ keywords = [ "sentry", "error-tracing", "fastapi", + "litestar", + "faststream", "structlog", ] classifiers = [ @@ -60,6 +62,7 @@ fastapi-all = [ ] litestar = [ "litestar>=2.9", + "prometheus-client>=0.20", ] litestar-otl = [ "opentelemetry-instrumentation-asgi>=0.46b0", diff --git a/tests/conftest.py b/tests/conftest.py index 869523d..3348de2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,11 +2,8 @@ from unittest.mock import Mock import pytest -from fastapi import FastAPI from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore[attr-defined] -from lite_bootstrap import ServiceConfig - class CustomInstrumentor(BaseInstrumentor): # type: ignore[misc] def instrumentation_dependencies(self) -> typing.Collection[str]: @@ -16,21 +13,6 @@ def _uninstrument(self, **kwargs: typing.Mapping[str, typing.Any]) -> None: pass -@pytest.fixture -def fastapi_app() -> FastAPI: - return FastAPI() - - -@pytest.fixture -def service_config() -> ServiceConfig: - return ServiceConfig( - service_name="microservice", - service_version="2.0.0", - service_environment="test", - service_debug=False, - ) - - @pytest.fixture(autouse=True) def mock_sentry_init(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("sentry_sdk.init", Mock) diff --git a/tests/instruments/test_logging_instrument.py b/tests/instruments/test_logging_instrument.py index 28d291c..8f443a9 100644 --- a/tests/instruments/test_logging_instrument.py +++ b/tests/instruments/test_logging_instrument.py @@ -5,32 +5,37 @@ from opentelemetry.sdk.trace.export import ConsoleSpanExporter from opentelemetry.trace import get_tracer -from lite_bootstrap.instruments.logging_instrument import LoggingInstrument, MemoryLoggerFactory -from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrument -from lite_bootstrap.service_config import ServiceConfig +from lite_bootstrap.instruments.logging_instrument import LoggingConfig, LoggingInstrument, MemoryLoggerFactory +from lite_bootstrap.instruments.opentelemetry_instrument import OpentelemetryConfig, OpenTelemetryInstrument logger = structlog.getLogger(__name__) -def test_logging_instrument(service_config: ServiceConfig) -> None: - logging_instrument = LoggingInstrument(logging_unset_handlers=["uvicorn"], logging_buffer_capacity=0) +def test_logging_instrument() -> None: + logging_instrument = LoggingInstrument( + bootstrap_config=LoggingConfig(logging_unset_handlers=["uvicorn"], logging_buffer_capacity=0) + ) try: - logging_instrument.bootstrap(service_config) + logging_instrument.bootstrap() logger.info("testing logging", key="value") finally: logging_instrument.teardown() -def test_logging_instrument_tracer_injection(service_config: ServiceConfig) -> None: - logging_instrument = LoggingInstrument(logging_unset_handlers=["uvicorn"], logging_buffer_capacity=0) +def test_logging_instrument_tracer_injection() -> None: + logging_instrument = LoggingInstrument( + bootstrap_config=LoggingConfig(logging_unset_handlers=["uvicorn"], logging_buffer_capacity=0) + ) opentelemetry_instrument = OpenTelemetryInstrument( - endpoint="otl", - span_exporter=ConsoleSpanExporter(), + bootstrap_config=OpentelemetryConfig( + opentelemetry_endpoint="otl", + opentelemetry_span_exporter=ConsoleSpanExporter(), + ) ) try: - logging_instrument.bootstrap(service_config) - opentelemetry_instrument.bootstrap(service_config) + logging_instrument.bootstrap() + opentelemetry_instrument.bootstrap() tracer = get_tracer(__name__) logger.info("testing tracer injection without spans") with tracer.start_as_current_span("my_fake_span") as span: diff --git a/tests/instruments/test_opentelemetry_instrument.py b/tests/instruments/test_opentelemetry_instrument.py index 740dcf4..b17b151 100644 --- a/tests/instruments/test_opentelemetry_instrument.py +++ b/tests/instruments/test_opentelemetry_instrument.py @@ -1,31 +1,38 @@ from opentelemetry.sdk.trace.export import ConsoleSpanExporter -from lite_bootstrap.instruments.opentelemetry_instrument import InstrumentorWithParams, OpenTelemetryInstrument -from lite_bootstrap.service_config import ServiceConfig +from lite_bootstrap.instruments.opentelemetry_instrument import ( + InstrumentorWithParams, + OpentelemetryConfig, + OpenTelemetryInstrument, +) from tests.conftest import CustomInstrumentor -def test_opentelemetry_instrument(service_config: ServiceConfig) -> None: +def test_opentelemetry_instrument() -> None: opentelemetry_instrument = OpenTelemetryInstrument( - endpoint="otl", - instrumentors=[ - InstrumentorWithParams(instrumentor=CustomInstrumentor(), additional_params={"key": "value"}), - CustomInstrumentor(), - ], - span_exporter=ConsoleSpanExporter(), + bootstrap_config=OpentelemetryConfig( + opentelemetry_endpoint="otl", + opentelemetry_instrumentors=[ + InstrumentorWithParams(instrumentor=CustomInstrumentor(), additional_params={"key": "value"}), + CustomInstrumentor(), + ], + opentelemetry_span_exporter=ConsoleSpanExporter(), + ) ) try: - opentelemetry_instrument.bootstrap(service_config) + opentelemetry_instrument.bootstrap() finally: opentelemetry_instrument.teardown() -def test_opentelemetry_instrument_empty_instruments(service_config: ServiceConfig) -> None: +def test_opentelemetry_instrument_empty_instruments() -> None: opentelemetry_instrument = OpenTelemetryInstrument( - endpoint="otl", - span_exporter=ConsoleSpanExporter(), + bootstrap_config=OpentelemetryConfig( + opentelemetry_endpoint="otl", + opentelemetry_span_exporter=ConsoleSpanExporter(), + ) ) try: - opentelemetry_instrument.bootstrap(service_config) + opentelemetry_instrument.bootstrap() finally: opentelemetry_instrument.teardown() diff --git a/tests/instruments/test_sentry_instrument.py b/tests/instruments/test_sentry_instrument.py index f054965..37500e2 100644 --- a/tests/instruments/test_sentry_instrument.py +++ b/tests/instruments/test_sentry_instrument.py @@ -1,10 +1,11 @@ -from lite_bootstrap.instruments.sentry_instrument import SentryInstrument -from lite_bootstrap.service_config import ServiceConfig +from lite_bootstrap.instruments.sentry_instrument import SentryConfig, SentryInstrument -def test_sentry_instrument(service_config: ServiceConfig) -> None: - SentryInstrument(dsn="https://testdsn@localhost/1", tags={"tag": "value"}).bootstrap(service_config) +def test_sentry_instrument() -> None: + SentryInstrument( + bootstrap_config=SentryConfig(sentry_dsn="https://testdsn@localhost/1", sentry_tags={"tag": "value"}) + ).bootstrap() -def test_sentry_instrument_empty_dsn(service_config: ServiceConfig) -> None: - SentryInstrument(dsn="").bootstrap(service_config) +def test_sentry_instrument_empty_dsn() -> None: + SentryInstrument(bootstrap_config=SentryConfig(sentry_dsn="")).bootstrap() diff --git a/tests/test_fastapi_bootstrap.py b/tests/test_fastapi_bootstrap.py index 66d18d1..2e9b21e 100644 --- a/tests/test_fastapi_bootstrap.py +++ b/tests/test_fastapi_bootstrap.py @@ -1,45 +1,31 @@ import structlog -from fastapi import FastAPI from opentelemetry.sdk.trace.export import ConsoleSpanExporter from starlette import status from starlette.testclient import TestClient -from lite_bootstrap import ( - FastAPIBootstrapper, - FastAPIHealthChecksInstrument, - FastAPILoggingInstrument, - FastAPIOpenTelemetryInstrument, - FastAPIPrometheusInstrument, - FastAPISentryInstrument, - ServiceConfig, -) +from lite_bootstrap import FastAPIBootstrapper, FastAPIConfig from tests.conftest import CustomInstrumentor logger = structlog.getLogger(__name__) -def test_fastapi_bootstrap(fastapi_app: FastAPI, service_config: ServiceConfig) -> None: +def test_fastapi_bootstrap() -> None: bootstrapper = FastAPIBootstrapper( - bootstrap_object=fastapi_app, - service_config=service_config, - instruments=[ - FastAPIOpenTelemetryInstrument( - endpoint="otl", - instrumentors=[CustomInstrumentor()], - span_exporter=ConsoleSpanExporter(), - ), - FastAPISentryInstrument( - dsn="https://testdsn@localhost/1", - ), - FastAPIHealthChecksInstrument( - path="/health/", - ), - FastAPILoggingInstrument(logging_buffer_capacity=0), - FastAPIPrometheusInstrument(), - ], + bootstrap_config=FastAPIConfig( + service_name="microservice", + service_version="2.0.0", + service_environment="test", + service_debug=False, + opentelemetry_endpoint="otl", + opentelemetry_instrumentors=[CustomInstrumentor()], + opentelemetry_span_exporter=ConsoleSpanExporter(), + sentry_dsn="https://testdsn@localhost/1", + health_checks_path="/health/", + logging_buffer_capacity=0, + ), ) - bootstrapper.bootstrap() + fastapi_app = bootstrapper.bootstrap() logger.info("testing logging", key="value") try: @@ -50,10 +36,15 @@ def test_fastapi_bootstrap(fastapi_app: FastAPI, service_config: ServiceConfig) 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) +def test_fastapi_prometheus_instrument() -> None: + prometheus_metrics_path = "/custom-metrics-path" + bootstrapper = FastAPIBootstrapper( + bootstrap_config=FastAPIConfig( + prometheus_metrics_path=prometheus_metrics_path, + ), + ) + fastapi_app = bootstrapper.bootstrap() - response = TestClient(fastapi_app).get(prometheus_instrument.metrics_path) + response = TestClient(fastapi_app).get(prometheus_metrics_path) assert response.status_code == status.HTTP_200_OK assert response.text diff --git a/tests/test_free_bootstrap.py b/tests/test_free_bootstrap.py index 913a947..2e58b5b 100644 --- a/tests/test_free_bootstrap.py +++ b/tests/test_free_bootstrap.py @@ -1,27 +1,22 @@ import structlog from opentelemetry.sdk.trace.export import ConsoleSpanExporter -from lite_bootstrap import FreeBootstrapper, LoggingInstrument, OpenTelemetryInstrument, SentryInstrument, ServiceConfig +from lite_bootstrap import FreeBootstrapper, FreeBootstrapperConfig from tests.conftest import CustomInstrumentor logger = structlog.getLogger(__name__) -def test_free_bootstrap(service_config: ServiceConfig) -> None: +def test_free_bootstrap() -> None: bootstrapper = FreeBootstrapper( - service_config=service_config, - instruments=[ - OpenTelemetryInstrument( - endpoint="otl", - instrumentors=[CustomInstrumentor()], - span_exporter=ConsoleSpanExporter(), - ), - SentryInstrument( - dsn="https://testdsn@localhost/1", - ), - LoggingInstrument(logging_buffer_capacity=0), - ], + bootstrap_config=FreeBootstrapperConfig( + opentelemetry_endpoint="otl", + opentelemetry_instrumentors=[CustomInstrumentor()], + opentelemetry_span_exporter=ConsoleSpanExporter(), + sentry_dsn="https://testdsn@localhost/1", + logging_buffer_capacity=0, + ), ) bootstrapper.bootstrap() try: diff --git a/tests/test_litestar_bootstrap.py b/tests/test_litestar_bootstrap.py index baebda9..379f3f8 100644 --- a/tests/test_litestar_bootstrap.py +++ b/tests/test_litestar_bootstrap.py @@ -1,44 +1,28 @@ import structlog from litestar import status_codes -from litestar.config.app import AppConfig from litestar.testing import TestClient from opentelemetry.sdk.trace.export import ConsoleSpanExporter -from lite_bootstrap import ( - LitestarBootstrapper, - LitestarHealthChecksInstrument, - LitestarLoggingInstrument, - LitestarOpenTelemetryInstrument, - LitestarPrometheusInstrument, - LitestarSentryInstrument, - ServiceConfig, -) +from lite_bootstrap import LitestarBootstrapper, LitestarConfig from tests.conftest import CustomInstrumentor logger = structlog.getLogger(__name__) -def test_litestar_bootstrap(service_config: ServiceConfig) -> None: - app_config = AppConfig() +def test_litestar_bootstrap() -> None: bootstrapper = LitestarBootstrapper( - bootstrap_object=app_config, - service_config=service_config, - instruments=[ - LitestarOpenTelemetryInstrument( - endpoint="otl", - instrumentors=[CustomInstrumentor()], - span_exporter=ConsoleSpanExporter(), - ), - LitestarSentryInstrument( - dsn="https://testdsn@localhost/1", - ), - LitestarHealthChecksInstrument( - path="/health/", - ), - LitestarLoggingInstrument(logging_buffer_capacity=0), - LitestarPrometheusInstrument(), - ], + bootstrap_config=LitestarConfig( + service_name="microservice", + service_version="2.0.0", + service_environment="test", + opentelemetry_endpoint="otl", + opentelemetry_instrumentors=[CustomInstrumentor()], + opentelemetry_span_exporter=ConsoleSpanExporter(), + sentry_dsn="https://testdsn@localhost/1", + health_checks_path="/health/", + logging_buffer_capacity=0, + ), ) application = bootstrapper.bootstrap() logger.info("testing logging", key="value") @@ -56,17 +40,14 @@ def test_litestar_bootstrap(service_config: ServiceConfig) -> None: bootstrapper.teardown() -def test_litestar_prometheus_bootstrap(service_config: ServiceConfig) -> None: - app_config = AppConfig() - prometheus_instrument = LitestarPrometheusInstrument(metrics_path="/custom-metrics-path") +def test_litestar_prometheus_bootstrap() -> None: + prometheus_metrics_path = "/custom-metrics-path" bootstrapper = LitestarBootstrapper( - bootstrap_object=app_config, - service_config=service_config, - instruments=[prometheus_instrument], + bootstrap_config=LitestarConfig(prometheus_metrics_path=prometheus_metrics_path), ) application = bootstrapper.bootstrap() with TestClient(app=application) as test_client: - response = test_client.get(prometheus_instrument.metrics_path) + response = test_client.get(prometheus_metrics_path) assert response.status_code == status_codes.HTTP_200_OK assert response.text