diff --git a/docs/introduction/configuration.md b/docs/introduction/configuration.md index 4742be6..b735811 100644 --- a/docs/introduction/configuration.md +++ b/docs/introduction/configuration.md @@ -31,7 +31,7 @@ Additional parameters can also be supplied through the settings object: - `sentry_traces_sample_rate` - in the range of 0.0 to 1.0, the percentage chance a given transaction will be sent - `sentry_sample_rate` - in the range of 0.0 to 1.0, the sample rate for error events - `sentry_max_breadcrumbs` - the total amount of breadcrumbs -- `sentry_max_value_length` - the max event payload length +- `sentry_max_value_length` - the maximum length of each string value before sentry-sdk truncates it - `sentry_attach_stacktrace` - if True, stack traces are automatically attached to all messages logged - `sentry_auto_session_tracking` - whether every request opens and closes a Sentry release-health `Session` (default: `False`), measured at ~7.7 µs per request. Set it to `True` if you use Sentry release health: crash-free session and user rates need it. - `sentry_integrations` - list of integrations to enable @@ -346,7 +346,7 @@ See [Performance](performance.md). Additional parameters: - `logging_enabled` - whether to configure structlog (default: `True`). -- `logging_log_level` +- `logging_log_level` - the root logger level set at bootstrap (default: `logging.INFO`). - `logging_extra_processors` - `logging_unset_handlers` - logger names whose handlers are cleared at bootstrap. These removals are permanent. - `logging_record_filters` - standard-library `logging.Filter` instances to attach to named loggers (see below). @@ -538,8 +538,6 @@ from lite_bootstrap import InstrumentDependencyMissingWarning warnings.filterwarnings("ignore", category=InstrumentDependencyMissingWarning) ``` -`InstrumentSkippedWarning` is kept as the base class for forward-compatibility (additional skip categories may emerge); today, `InstrumentDependencyMissingWarning` is its only concrete subclass. - To inspect skipped instruments programmatically: ```python diff --git a/docs/migration/to-2.x.md b/docs/migration/to-2.x.md index e0dfe6d..fada91f 100644 --- a/docs/migration/to-2.x.md +++ b/docs/migration/to-2.x.md @@ -26,3 +26,20 @@ traffic mix. JSON log lines include `tracing` only when emitted inside a recording OpenTelemetry span. The object contains `span_id` and `trace_id`. Update log parsers to treat `tracing` as optional. + +## Missing-dependency warning category + +`InstrumentSkippedWarning` has been removed. Importing it from `lite_bootstrap` +now raises `ImportError`. Replace imports and warning filters with +`InstrumentDependencyMissingWarning`, which now derives directly from `UserWarning`: + +```python +import warnings + +from lite_bootstrap import InstrumentDependencyMissingWarning + +warnings.filterwarnings("error", category=InstrumentDependencyMissingWarning) +``` + +Unconfigured instruments continue to be skipped silently. A configured instrument +with a missing optional dependency emits `InstrumentDependencyMissingWarning`. diff --git a/lite_bootstrap/__init__.py b/lite_bootstrap/__init__.py index 24a036a..372f3ed 100644 --- a/lite_bootstrap/__init__.py +++ b/lite_bootstrap/__init__.py @@ -7,7 +7,6 @@ BootstrapperNotReadyError, ConfigurationError, InstrumentDependencyMissingWarning, - InstrumentSkippedWarning, LiteBootstrapError, TeardownError, ) @@ -26,7 +25,6 @@ "FreeBootstrapper", "FreeConfig", "InstrumentDependencyMissingWarning", - "InstrumentSkippedWarning", "LiteBootstrapError", "LitestarBootstrapper", "LitestarConfig", diff --git a/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py b/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py index e7626c7..146f4ff 100644 --- a/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py @@ -296,8 +296,6 @@ def bootstrap(self) -> None: class FastAPIBootstrapper(BaseBootstrapper["fastapi.FastAPI", FastAPIConfig]): - __slots__ = "bootstrap_config", "instruments" - instruments_types: typing.ClassVar = [ FastAPICorsInstrument, FastAPIOpenTelemetryInstrument, diff --git a/lite_bootstrap/bootstrappers/fastmcp_bootstrapper.py b/lite_bootstrap/bootstrappers/fastmcp_bootstrapper.py index 494ffc3..b418997 100644 --- a/lite_bootstrap/bootstrappers/fastmcp_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/fastmcp_bootstrapper.py @@ -370,8 +370,6 @@ def bootstrap(self) -> None: class FastMcpBootstrapper(BaseBootstrapper["FastMCP[typing.Any]", FastMcpConfig]): - __slots__ = "bootstrap_config", "instruments" - instruments_types: typing.ClassVar = [ FastMcpOpenTelemetryInstrument, PyroscopeInstrument, diff --git a/lite_bootstrap/bootstrappers/faststream_bootstrapper.py b/lite_bootstrap/bootstrappers/faststream_bootstrapper.py index e7e7dc0..3650229 100644 --- a/lite_bootstrap/bootstrappers/faststream_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/faststream_bootstrapper.py @@ -54,7 +54,6 @@ async def after_processed( tracer: typing.Final = trace.get_tracer(__name__) -@typing.runtime_checkable class FastStreamTelemetryMiddlewareProtocol(typing.Protocol): def __init__( self, @@ -71,7 +70,6 @@ def __init__( def __call__(self, msg: typing.Any, /, *, context: typing.Any) -> typing.Any: ... # noqa: ANN401 -@typing.runtime_checkable class FastStreamPrometheusMiddlewareProtocol(typing.Protocol): def __init__( self, @@ -266,8 +264,6 @@ def bootstrap(self) -> None: class FastStreamBootstrapper(BaseBootstrapper["AsgiFastStream", FastStreamConfig]): - __slots__ = "bootstrap_config", "instruments" - instruments_types: typing.ClassVar = [ FastStreamOpenTelemetryInstrument, PyroscopeInstrument, diff --git a/lite_bootstrap/bootstrappers/free_bootstrapper.py b/lite_bootstrap/bootstrappers/free_bootstrapper.py index 2428ab5..95edb0a 100644 --- a/lite_bootstrap/bootstrappers/free_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/free_bootstrapper.py @@ -13,8 +13,6 @@ class FreeConfig(LoggingConfig, OpenTelemetryConfig, PyroscopeConfig, SentryConf class FreeBootstrapper(BaseBootstrapper[None, FreeConfig]): - __slots__ = "bootstrap_config", "instruments" - instruments_types: typing.ClassVar = [ LoggingInstrument, SentryInstrument, diff --git a/lite_bootstrap/bootstrappers/litestar_bootstrapper.py b/lite_bootstrap/bootstrappers/litestar_bootstrapper.py index 4a2acea..9df237f 100644 --- a/lite_bootstrap/bootstrappers/litestar_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/litestar_bootstrapper.py @@ -485,8 +485,6 @@ def bootstrap(self) -> None: class LitestarBootstrapper(BaseBootstrapper["litestar.Litestar", LitestarConfig]): - __slots__ = "bootstrap_config", "instruments" - instruments_types: typing.ClassVar = [ LitestarCorsInstrument, LitestarOpenTelemetryInstrument, diff --git a/lite_bootstrap/exceptions.py b/lite_bootstrap/exceptions.py index c90ab62..ac7dba7 100644 --- a/lite_bootstrap/exceptions.py +++ b/lite_bootstrap/exceptions.py @@ -12,10 +12,11 @@ class BootstrapperNotReadyError(LiteBootstrapError): class ConfigurationError(LiteBootstrapError): - """Raised when a config is invalid or a required optional dependency is missing. + """Raised for invalid configuration or when bootstrap() cannot use the application. - Also raised when a bootstrapper is constructed on an application another bootstrapper - already owns. + bootstrap() rejects an application owned by another bootstrapper and refuses to reuse + a bootstrapper after teardown or a failed bootstrap. Missing optional dependencies warn + instead, as does construction on an application another bootstrapper already owns. """ @@ -28,11 +29,7 @@ def __init__(self, errors: list[tuple[str, BaseException]]) -> None: super().__init__(f"{len(errors)} instrument(s) failed during teardown: {details}") -class InstrumentSkippedWarning(UserWarning): - """Base class for warnings emitted when an instrument is skipped during bootstrap.""" - - -class InstrumentDependencyMissingWarning(InstrumentSkippedWarning): +class InstrumentDependencyMissingWarning(UserWarning): """Emitted when an instrument is skipped because its optional dependency is not installed.""" diff --git a/pyproject.toml b/pyproject.toml index d2f42f7..d9f4122 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,7 +169,6 @@ faststream-metrics = [ ] faststream-all = [ "lite-bootstrap[faststream-sentry,faststream-otl,faststream-logging,faststream-metrics,pyroscope]", - "lite-bootstrap[sentry,otl,logging,faststream,faststream-metrics,pyroscope]" ] fastmcp = [ # >=4.0 so one middleware dispatch model is supported: from 4.0 the access log's on_message sees diff --git a/scripts/floor_smoke.py b/scripts/floor_smoke.py index 65ef1cf..af5414d 100644 --- a/scripts/floor_smoke.py +++ b/scripts/floor_smoke.py @@ -9,7 +9,7 @@ config with every instrument its extras can supply, bootstraps, exercises the calls that set a floor, and tears down. -`InstrumentSkippedWarning` is escalated to an error, because an instrument that +`InstrumentDependencyMissingWarning` is escalated to an error, because an instrument that degrades to a silent skip would let a too-low floor pass as success. Not a pytest test: conftest.py hard-imports opentelemetry, sentry_sdk and structlog, @@ -31,7 +31,7 @@ FastStreamConfig, FreeBootstrapper, FreeConfig, - InstrumentSkippedWarning, + InstrumentDependencyMissingWarning, LitestarBootstrapper, LitestarConfig, import_checker, @@ -338,7 +338,7 @@ def main() -> None: expected_argv_len = 2 if len(sys.argv) != expected_argv_len or sys.argv[1] not in TARGETS: sys.exit(f"usage: floor_smoke.py {{{'|'.join(TARGETS)}}}") - warnings.filterwarnings("error", category=InstrumentSkippedWarning) + warnings.filterwarnings("error", category=InstrumentDependencyMissingWarning) target = sys.argv[1] TARGETS[target]() print(f"floor smoke OK: {target} on {sys.version}") # noqa: T201 diff --git a/tests/conftest.py b/tests/conftest.py index 605ad42..f5b7541 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,18 +24,18 @@ from structlog.typing import EventDict, WrappedLogger from lite_bootstrap import import_checker -from lite_bootstrap.exceptions import InstrumentSkippedWarning +from lite_bootstrap.exceptions import InstrumentDependencyMissingWarning from lite_bootstrap.instruments import healthchecks_instrument, opentelemetry_instrument def pytest_configure() -> None: - """Escalate InstrumentSkippedWarning (and subclasses) to errors. + """Escalate InstrumentDependencyMissingWarning (and subclasses) to errors. Done here rather than via filterwarnings in pyproject.toml to avoid pytest resolving the warning class before pytest-cov starts coverage, which would cause coverage to miss the lite_bootstrap module-level statements. """ - warnings.filterwarnings("error", category=InstrumentSkippedWarning) + warnings.filterwarnings("error", category=InstrumentDependencyMissingWarning) class CustomInstrumentor(BaseInstrumentor):