Skip to content

Commit 2db731f

Browse files
authored
Merge pull request #86 from modern-python/fix/instrument-skip-warnings
feat: unify instrument skip feedback through warning subclasses
2 parents 735f2ed + 992b3db commit 2db731f

9 files changed

Lines changed: 82 additions & 24 deletions

File tree

docs/integrations/fastapi.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ bootstrapper_config = FastAPIConfig(
3434
service_name="microservice",
3535
service_version="2.0.0",
3636
service_environment="test",
37-
service_debug=False,
3837
cors_allowed_origins=["http://test"],
3938
health_checks_path="/custom-health/",
4039
opentelemetry_endpoint="otl",

docs/integrations/faststream.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ bootstrapper_config = FastStreamConfig(
3737
service_name="microservice",
3838
service_version="2.0.0",
3939
service_environment="test",
40-
service_debug=False,
4140
opentelemetry_endpoint="otl",
4241
opentelemetry_middleware_cls=RedisTelemetryMiddleware,
4342
prometheus_metrics_path="/custom-metrics/",

docs/integrations/free.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ from lite_bootstrap import FreeBootstrapperConfig, FreeBootstrapper
2929

3030

3131
bootstrapper_config = FreeBootstrapperConfig(
32-
service_debug=False,
3332
opentelemetry_endpoint="otl",
3433
sentry_dsn="https://testdsn@localhost/1",
3534
)

docs/integrations/litestar.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ bootstrapper_config = LitestarConfig(
3434
service_name="microservice",
3535
service_version="2.0.0",
3636
service_environment="test",
37-
service_debug=False,
3837
cors_allowed_origins=["http://test"],
3938
health_checks_path="/custom-health/",
4039
opentelemetry_endpoint="otl",

docs/introduction/configuration.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,11 @@ When OpenTelemetry is also enabled, a `PyroscopeSpanProcessor` is automatically
105105

106106
## Structlog
107107

108-
To bootstrap Structlog, you must set `service_debug` to False
108+
Structlog is bootstrapped by default. To opt out, set `logging_enabled=False`.
109109

110110
Additional parameters:
111111

112+
- `logging_enabled` - whether to configure structlog (default: `True`).
112113
- `logging_log_level`
113114
- `logging_flush_level`
114115
- `logging_buffer_capacity`
@@ -121,7 +122,6 @@ import structlog
121122
from lite_bootstrap import FastAPIConfig
122123

123124
config = FastAPIConfig(
124-
service_debug=False,
125125
logging_time_stamper=structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=True),
126126
)
127127
```
@@ -157,7 +157,6 @@ import logging
157157
from lite_bootstrap import FastStreamConfig
158158

159159
config = FastStreamConfig(
160-
service_debug=False,
161160
logging_log_level=logging.INFO, # your application logs
162161
faststream_log_level=logging.WARNING, # broker "Received"/"Processed" messages (default)
163162
)
@@ -193,3 +192,29 @@ Additional params:
193192

194193
- `health_checks_path`
195194
- `health_checks_include_in_schema`
195+
196+
## Skipped instrument warnings
197+
198+
When a bootstrapper is constructed, each registered instrument is checked. If it can't run, the instrument is skipped and a `UserWarning` subclass is emitted so the skip is visible at the call site:
199+
200+
- `InstrumentDependencyMissingWarning` — the instrument's optional package is not installed (e.g. `[sentry]` extra missing).
201+
- `InstrumentNotReadyWarning` — the instrument's required config is missing or disabled (e.g. `sentry_dsn` not set, `logging_enabled=False`, `pyroscope_endpoint` empty).
202+
- `InstrumentSkippedWarning` — base class for both, useful if you want to filter every skip with one rule.
203+
204+
Both go through Python's `warnings` module, so they show up in stderr by default and can be filtered, captured, or escalated like any other warning. Example — silence intentional opt-outs but keep dependency-missing warnings loud:
205+
206+
```python
207+
import warnings
208+
from lite_bootstrap import InstrumentNotReadyWarning
209+
210+
warnings.filterwarnings("ignore", category=InstrumentNotReadyWarning)
211+
```
212+
213+
Or treat any skip as an error in CI:
214+
215+
```python
216+
import warnings
217+
from lite_bootstrap import InstrumentSkippedWarning
218+
219+
warnings.filterwarnings("error", category=InstrumentSkippedWarning)
220+
```

lite_bootstrap/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from lite_bootstrap.exceptions import (
66
BootstrapperNotReadyError,
77
ConfigurationError,
8+
InstrumentDependencyMissingWarning,
9+
InstrumentNotReadyWarning,
10+
InstrumentSkippedWarning,
811
LiteBootstrapError,
912
TeardownError,
1013
)
@@ -20,6 +23,9 @@
2023
"FastStreamConfig",
2124
"FreeBootstrapper",
2225
"FreeBootstrapperConfig",
26+
"InstrumentDependencyMissingWarning",
27+
"InstrumentNotReadyWarning",
28+
"InstrumentSkippedWarning",
2329
"LiteBootstrapError",
2430
"LitestarBootstrapper",
2531
"LitestarConfig",

lite_bootstrap/bootstrappers/base.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
import typing
44
import warnings
55

6-
from lite_bootstrap.exceptions import BootstrapperNotReadyError, TeardownError
6+
from lite_bootstrap.exceptions import (
7+
BootstrapperNotReadyError,
8+
InstrumentDependencyMissingWarning,
9+
InstrumentNotReadyWarning,
10+
TeardownError,
11+
)
712
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
813
from lite_bootstrap.types import ApplicationT
914

@@ -33,16 +38,26 @@ def __init__(self, bootstrap_config: BaseConfig) -> None:
3338
self.bootstrap_config = bootstrap_config
3439
self.instruments = []
3540
for instrument_type in self.instruments_types:
36-
instrument = instrument_type(bootstrap_config=bootstrap_config)
37-
if not instrument.check_dependencies():
38-
warnings.warn(instrument.missing_dependency_message, stacklevel=2)
39-
continue
40-
41-
if not instrument.is_ready():
42-
logger.info(f"{instrument_type.__name__} is not ready: {instrument.not_ready_message}")
43-
continue
44-
45-
self.instruments.append(instrument)
41+
if (instrument := self._register_or_skip(instrument_type)) is not None:
42+
self.instruments.append(instrument)
43+
44+
def _register_or_skip(self, instrument_type: type[BaseInstrument]) -> BaseInstrument | None:
45+
instrument = instrument_type(bootstrap_config=self.bootstrap_config)
46+
if not instrument.check_dependencies():
47+
warnings.warn(
48+
instrument.missing_dependency_message,
49+
category=InstrumentDependencyMissingWarning,
50+
stacklevel=4,
51+
)
52+
return None
53+
if not instrument.is_ready():
54+
warnings.warn(
55+
f"{instrument_type.__name__} is not ready: {instrument.not_ready_message}",
56+
category=InstrumentNotReadyWarning,
57+
stacklevel=4,
58+
)
59+
return None
60+
return instrument
4661

4762
@property
4863
@abc.abstractmethod

lite_bootstrap/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,15 @@ def __init__(self, errors: list[tuple[str, BaseException]]) -> None:
1717
self.errors = errors
1818
details = "; ".join(f"{name}: {err}" for name, err in errors)
1919
super().__init__(f"{len(errors)} instrument(s) failed during teardown: {details}")
20+
21+
22+
class InstrumentSkippedWarning(UserWarning):
23+
"""Base class for warnings emitted when an instrument is skipped during bootstrap."""
24+
25+
26+
class InstrumentDependencyMissingWarning(InstrumentSkippedWarning):
27+
"""Emitted when an instrument is skipped because its optional dependency is not installed."""
28+
29+
30+
class InstrumentNotReadyWarning(InstrumentSkippedWarning):
31+
"""Emitted when an instrument is skipped because its config indicates it should not run."""

tests/test_free_bootstrap.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
import structlog
55
from structlog.testing import capture_logs
66

7-
from lite_bootstrap import FreeBootstrapper, FreeBootstrapperConfig, TeardownError
7+
from lite_bootstrap import (
8+
FreeBootstrapper,
9+
FreeBootstrapperConfig,
10+
InstrumentNotReadyWarning,
11+
TeardownError,
12+
)
813
from tests.conftest import CustomInstrumentor, SentryTestTransport, emulate_package_missing
914

1015

@@ -32,7 +37,7 @@ def test_free_bootstrap(free_bootstrapper_config: FreeBootstrapperConfig) -> Non
3237

3338

3439
def test_free_bootstrap_logging_disabled() -> None:
35-
with capture_logs() as cap_logs:
40+
with pytest.warns(InstrumentNotReadyWarning) as records:
3641
FreeBootstrapper(
3742
bootstrap_config=FreeBootstrapperConfig(
3843
logging_enabled=False,
@@ -43,10 +48,9 @@ def test_free_bootstrap_logging_disabled() -> None:
4348
logging_buffer_capacity=0,
4449
),
4550
)
46-
assert cap_logs == [
47-
{"event": "LoggingInstrument is not ready: logging_enabled is False", "log_level": "info"},
48-
{"event": "PyroscopeInstrument is not ready: pyroscope_endpoint is empty", "log_level": "info"},
49-
]
51+
messages = [str(r.message) for r in records]
52+
assert "LoggingInstrument is not ready: logging_enabled is False" in messages
53+
assert "PyroscopeInstrument is not ready: pyroscope_endpoint is empty" in messages
5054

5155

5256
def test_teardown_error_isolation(free_bootstrapper_config: FreeBootstrapperConfig) -> None:

0 commit comments

Comments
 (0)