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: 2 additions & 2 deletions lite_bootstrap/bootstrappers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BaseBootstrapper(abc.ABC, typing.Generic[ApplicationT]):
def __init__(self, bootstrap_config: BaseConfig) -> None:
self.is_bootstrapped = False
if not self.is_ready():
msg = f"{type(self).__name__} is not ready because {self.not_ready_message}"
msg = f"{type(self).__name__} is not ready: {self.not_ready_message}"
raise RuntimeError(msg)

self.bootstrap_config = bootstrap_config
Expand All @@ -38,7 +38,7 @@ def __init__(self, bootstrap_config: BaseConfig) -> None:
continue

if not instrument.is_ready():
logger.info(f"{instrument_type.__name__} is not ready, because {instrument.not_ready_message}")
logger.info(f"{instrument_type.__name__} is not ready: {instrument.not_ready_message}")
continue

self.instruments.append(instrument)
Expand Down
2 changes: 1 addition & 1 deletion lite_bootstrap/bootstrappers/fastapi_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class FastAPISwaggerInstrument(SwaggerInstrument):
def bootstrap(self) -> None:
if self.bootstrap_config.swagger_path != self.bootstrap_config.application.docs_url:
warnings.warn(
f"swagger_path is differ from docs_url, "
f"swagger_path differs from docs_url, "
f"{self.bootstrap_config.application.docs_url} will be used for docs path",
stacklevel=2,
)
Expand Down
2 changes: 1 addition & 1 deletion lite_bootstrap/helpers/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import typing


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


def is_valid_path(maybe_path: str) -> bool:
Expand Down
32 changes: 27 additions & 5 deletions lite_bootstrap/instruments/logging_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(
self.logging_flush_level = logging_flush_level
self.logging_log_level = logging_log_level
self.log_stream = log_stream
self._created_handlers: list[tuple[logging.Logger, logging.handlers.MemoryHandler]] = []

def __call__(self, *args: typing.Any) -> logging.Logger: # noqa: ANN401
logger: typing.Final = super().__call__(*args)
Expand All @@ -80,8 +81,19 @@ def __call__(self, *args: typing.Any) -> logging.Logger: # noqa: ANN401
logger.addHandler(handler)
logger.setLevel(self.logging_log_level)
logger.propagate = False
self._created_handlers.append((logger, handler))
return logger

def close_handlers(self) -> None:
for created_logger, handler in self._created_handlers:
created_logger.removeHandler(handler)
created_logger.propagate = True
target = handler.target
handler.close()
if target is not None:
target.close()
self._created_handlers.clear()

def _serialize_log_with_orjson_to_string(value: typing.Any, **kwargs: typing.Any) -> str: # noqa: ANN401
return orjson.dumps(value, **kwargs).decode()

Expand All @@ -103,6 +115,9 @@ class LoggingInstrument(BaseInstrument):
bootstrap_config: LoggingConfig
not_ready_message = "service_debug is True"
missing_dependency_message = "structlog is not installed"
_logger_factory: "MemoryLoggerFactory | None" = dataclasses.field(
default_factory=lambda: None, init=False, repr=False, compare=False
)

@property
def structlog_pre_chain_processors(self) -> list[typing.Any]:
Expand Down Expand Up @@ -139,11 +154,15 @@ def structlog_processors(self) -> list[typing.Any]:

@property
def memory_logger_factory(self) -> "MemoryLoggerFactory":
return MemoryLoggerFactory(
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,
)
cached: MemoryLoggerFactory | None = self._logger_factory
if cached is None:
cached = MemoryLoggerFactory(
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,
)
object.__setattr__(self, "_logger_factory", cached)
return cached

def _configure_structlog_loggers(self) -> None:
structlog.configure(
Expand Down Expand Up @@ -183,3 +202,6 @@ def teardown(self) -> None:
root_logger.removeHandler(h)
h.close()
root_logger.setLevel(logging.WARNING)
if self._logger_factory is not None:
self._logger_factory.close_handlers()
object.__setattr__(self, "_logger_factory", None)
2 changes: 0 additions & 2 deletions tests/instruments/test_sentry_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import pytest
import sentry_sdk
import structlog
from sentry_sdk.integrations.logging import LoggingIntegration

from lite_bootstrap.instruments.logging_instrument import LoggingConfig, LoggingInstrument
from tests.conftest import LoggingMock, SentryTestTransport
Expand Down Expand Up @@ -62,7 +61,6 @@ def test_sentry_instrument_with_structlog_error(
logger.error("some error")
logger.error("some error, skipping sentry", skip_sentry=True)
assert len(sentry_mock.mock_envelopes) == 1
LoggingIntegration()
finally:
sentry_sdk.init()
logging_instrument.teardown()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_fastapi_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_fastapi_bootstrapper_not_ready() -> None:
def test_fastapi_bootstrapper_docs_url_differ(fastapi_config: FastAPIConfig) -> None:
new_config = dataclasses.replace(fastapi_config, application=fastapi.FastAPI(docs_url="/custom-docs/"))
bootstrapper = FastAPIBootstrapper(bootstrap_config=new_config)
with pytest.warns(UserWarning, match="swagger_path is differ from docs_url"):
with pytest.warns(UserWarning, match="swagger_path differs from docs_url"):
bootstrapper.bootstrap()
bootstrapper.teardown()

Expand Down
4 changes: 2 additions & 2 deletions tests/test_free_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def test_free_bootstrap_logging_not_ready() -> None:
),
)
assert cap_logs == [
{"event": "LoggingInstrument is not ready, because service_debug is True", "log_level": "info"},
{"event": "PyroscopeInstrument is not ready, because pyroscope_endpoint is empty", "log_level": "info"},
{"event": "LoggingInstrument is not ready: service_debug is True", "log_level": "info"},
{"event": "PyroscopeInstrument is not ready: pyroscope_endpoint is empty", "log_level": "info"},
]


Expand Down
Loading