Skip to content

Commit 56582ee

Browse files
authored
Merge pull request #66 from modern-python/refactor
refactor tests
2 parents 2c83462 + c19bca0 commit 56582ee

11 files changed

+105
-41
lines changed

lite_bootstrap/instruments/logging_instrument.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import sys
55
import typing
66

7+
import orjson
8+
79
from lite_bootstrap import import_checker
810
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
911

@@ -13,9 +15,7 @@
1315

1416

1517
if import_checker.is_structlog_installed:
16-
import orjson
1718
import structlog
18-
from structlog.processors import ExceptionRenderer
1919

2020

2121
ScopeType = typing.MutableMapping[str, typing.Any]
@@ -97,15 +97,6 @@ class LoggingConfig(BaseConfig):
9797
)
9898

9999

100-
class CustomExceptionRenderer(ExceptionRenderer):
101-
def __call__(self, logger: "WrappedLogger", name: str, event_dict: "EventDict") -> "EventDict":
102-
exc_info = event_dict.get("exc_info")
103-
event_dict = super().__call__(logger=logger, name=name, event_dict=event_dict)
104-
if exc_info:
105-
event_dict["exc_info"] = exc_info
106-
return event_dict
107-
108-
109100
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
110101
class LoggingInstrument(BaseInstrument):
111102
bootstrap_config: LoggingConfig
@@ -121,7 +112,7 @@ def structlog_pre_chain_processors(self) -> list[typing.Any]:
121112
structlog.stdlib.PositionalArgumentsFormatter(),
122113
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
123114
structlog.processors.StackInfoRenderer(),
124-
CustomExceptionRenderer(),
115+
structlog.processors.format_exc_info,
125116
structlog.processors.UnicodeDecoder(),
126117
]
127118

@@ -162,7 +153,8 @@ def _configure_foreign_loggers(self) -> None:
162153
foreign_pre_chain=self.structlog_pre_chain_processors,
163154
processors=[
164155
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
165-
structlog.processors.JSONRenderer(),
156+
*self.bootstrap_config.logging_extra_processors,
157+
structlog.processors.JSONRenderer(serializer=_serialize_log_with_orjson_to_string),
166158
],
167159
logger=root_logger,
168160
)
@@ -177,3 +169,8 @@ def bootstrap(self) -> None:
177169

178170
def teardown(self) -> None:
179171
structlog.reset_defaults()
172+
root_logger = logging.getLogger()
173+
for h in root_logger.handlers[:]:
174+
root_logger.removeHandler(h)
175+
h.close()
176+
root_logger.setLevel(logging.WARNING)

lite_bootstrap/instruments/opentelemetry_instrument.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def bootstrap(self) -> None:
7070
tracer_provider = TracerProvider(resource=resource)
7171
if self.bootstrap_config.opentelemetry_log_traces:
7272
tracer_provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
73-
if self.bootstrap_config.opentelemetry_endpoint:
73+
if self.bootstrap_config.opentelemetry_endpoint: # pragma: no cover
7474
tracer_provider.add_span_processor(
7575
BatchSpanProcessor(
7676
OTLPSpanExporter(

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ classifiers = [
2929
"Topic :: Software Development :: Libraries",
3030
]
3131
version = "0"
32+
dependencies = [
33+
"orjson",
34+
]
3235

3336
[project.urls]
3437
repository = "https://github.com/modern-python/lite-bootstrap"
@@ -45,7 +48,6 @@ otl = [
4548
]
4649
logging = [
4750
"structlog",
48-
"orjson",
4951
]
5052
free-all = [
5153
"lite-bootstrap[sentry,otl,logging]",

tests/conftest.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import sys
33
import typing
44
from importlib import reload
5-
from unittest.mock import Mock
65

76
import pytest
7+
import sentry_sdk
88
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore[attr-defined]
99
from structlog.testing import capture_logs
1010
from structlog.typing import EventDict
@@ -20,9 +20,21 @@ def _uninstrument(self, **kwargs: typing.Mapping[str, typing.Any]) -> None:
2020
pass
2121

2222

23-
@pytest.fixture(autouse=True)
24-
def mock_sentry_init(monkeypatch: pytest.MonkeyPatch) -> None:
25-
monkeypatch.setattr("sentry_sdk.init", Mock)
23+
P = typing.ParamSpec("P")
24+
25+
26+
class SentryTestTransport(sentry_sdk.Transport):
27+
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: # noqa: ANN401
28+
super().__init__(*args, **kwargs)
29+
self.mock_envelopes: list[sentry_sdk.envelope.Envelope] = []
30+
31+
def capture_envelope(self, envelope: sentry_sdk.envelope.Envelope) -> None:
32+
self.mock_envelopes.append(envelope)
33+
34+
35+
@pytest.fixture
36+
def sentry_mock() -> SentryTestTransport:
37+
return SentryTestTransport()
2638

2739

2840
@contextlib.contextmanager

tests/instruments/test_logging_instrument.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,79 @@
33

44
import structlog
55
from opentelemetry.trace import get_tracer
6+
from structlog.testing import LogCapture
67

78
from lite_bootstrap.instruments.logging_instrument import LoggingConfig, LoggingInstrument, MemoryLoggerFactory
89
from lite_bootstrap.instruments.opentelemetry_instrument import OpentelemetryConfig, OpenTelemetryInstrument
910

1011

11-
logger = structlog.getLogger(__name__)
1212
std_logger = logging.getLogger(__name__)
1313

1414

1515
def test_logging_instrument_simple() -> None:
16+
log_capture = LogCapture()
1617
logging_instrument = LoggingInstrument(
1718
bootstrap_config=LoggingConfig(
18-
logging_unset_handlers=["uvicorn"], logging_buffer_capacity=0, service_debug=False
19+
logging_unset_handlers=["uvicorn"],
20+
logging_buffer_capacity=0,
21+
service_debug=False,
22+
logging_extra_processors=[log_capture],
1923
)
2024
)
2125
try:
2226
logging_instrument.bootstrap()
27+
28+
logger = structlog.getLogger(__name__)
2329
logger.info("testing structlog", key="value")
30+
std_logger.info("testing std logger", extra={"key": "value"})
2431
try:
2532
msg = "some error"
2633
raise ValueError(msg) # noqa: TRY301
2734
except ValueError:
2835
logger.exception("logging error")
29-
std_logger.info("testing std logger", extra={"key": "value"})
36+
37+
events_number = 2
38+
assert len(log_capture.entries) == events_number
3039
finally:
3140
logging_instrument.teardown()
3241

3342

3443
def test_logging_instrument_tracer_injection() -> None:
44+
log_capture = LogCapture()
3545
logging_instrument = LoggingInstrument(
36-
bootstrap_config=LoggingConfig(logging_unset_handlers=["uvicorn"], logging_buffer_capacity=0)
46+
bootstrap_config=LoggingConfig(
47+
logging_unset_handlers=["uvicorn"],
48+
logging_buffer_capacity=0,
49+
logging_extra_processors=[log_capture],
50+
)
3751
)
3852
opentelemetry_instrument = OpenTelemetryInstrument(
3953
bootstrap_config=OpentelemetryConfig(
40-
opentelemetry_endpoint="otl",
4154
opentelemetry_log_traces=True,
4255
)
4356
)
4457
try:
4558
logging_instrument.bootstrap()
4659
opentelemetry_instrument.bootstrap()
60+
61+
logger = structlog.getLogger(__name__)
4762
tracer = get_tracer(__name__)
4863
logger.info("testing tracer injection without spans")
4964
with tracer.start_as_current_span("my_fake_span") as span:
5065
logger.info("testing tracer injection without span attributes")
5166
span.set_attribute("example_attribute", "value")
5267
span.add_event("example_event", {"event_attr": 1})
5368
logger.info("testing tracer injection with span attributes")
69+
70+
assert log_capture.entries[0]["event"] == "testing tracer injection without spans"
71+
72+
assert log_capture.entries[1]["event"] == "testing tracer injection without span attributes"
73+
assert log_capture.entries[2]["event"] == "testing tracer injection with span attributes"
74+
75+
tracing1 = log_capture.entries[1]["tracing"]
76+
tracing2 = log_capture.entries[2]["tracing"]
77+
assert tracing1
78+
assert tracing1 == tracing2
5479
finally:
5580
logging_instrument.teardown()
5681
opentelemetry_instrument.teardown()

tests/instruments/test_opentelemetry_instrument.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
def test_opentelemetry_instrument() -> None:
1010
opentelemetry_instrument = OpenTelemetryInstrument(
1111
bootstrap_config=OpentelemetryConfig(
12-
opentelemetry_endpoint="otl",
1312
opentelemetry_instrumentors=[
1413
InstrumentorWithParams(instrumentor=CustomInstrumentor(), additional_params={"key": "value"}),
1514
CustomInstrumentor(),
@@ -26,7 +25,6 @@ def test_opentelemetry_instrument() -> None:
2625
def test_opentelemetry_instrument_empty_instruments() -> None:
2726
opentelemetry_instrument = OpenTelemetryInstrument(
2827
bootstrap_config=OpentelemetryConfig(
29-
opentelemetry_endpoint="otl",
3028
opentelemetry_log_traces=True,
3129
)
3230
)

tests/instruments/test_sentry_instrument.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,41 @@
1-
from lite_bootstrap.instruments.sentry_instrument import SentryConfig, SentryInstrument
1+
import logging
2+
import typing
23

4+
import pytest
5+
import sentry_sdk
36

4-
def test_sentry_instrument() -> None:
5-
SentryInstrument(
6-
bootstrap_config=SentryConfig(sentry_dsn="https://testdsn@localhost/1", sentry_tags={"tag": "value"})
7-
).bootstrap()
7+
from tests.conftest import SentryTestTransport
8+
9+
10+
if typing.TYPE_CHECKING:
11+
pass
12+
13+
from lite_bootstrap.instruments.sentry_instrument import (
14+
SentryConfig,
15+
SentryInstrument,
16+
)
17+
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
@pytest.fixture
23+
def minimal_sentry_config(sentry_mock: SentryTestTransport) -> SentryConfig:
24+
return SentryConfig(
25+
sentry_dsn="https://testdsn@localhost/1",
26+
sentry_tags={"test": "test"},
27+
sentry_additional_params={"transport": sentry_mock},
28+
)
29+
30+
31+
def test_sentry_instrument_with_raise(minimal_sentry_config: SentryConfig, sentry_mock: SentryTestTransport) -> None:
32+
SentryInstrument(bootstrap_config=minimal_sentry_config).bootstrap()
33+
34+
try:
35+
logger.error("some error")
36+
assert len(sentry_mock.mock_envelopes) == 1
37+
finally:
38+
sentry_sdk.init()
839

940

1041
def test_sentry_instrument_empty_dsn() -> None:

tests/test_fastapi_bootstrap.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from starlette.testclient import TestClient
99

1010
from lite_bootstrap import FastAPIBootstrapper, FastAPIConfig
11-
from tests.conftest import CustomInstrumentor, emulate_package_missing
11+
from tests.conftest import CustomInstrumentor, SentryTestTransport, emulate_package_missing
1212

1313

1414
logger = structlog.getLogger(__name__)
@@ -25,12 +25,12 @@ def fastapi_config() -> FastAPIConfig:
2525
cors_allowed_origins=["http://test"],
2626
health_checks_path="/custom-health/",
2727
logging_buffer_capacity=0,
28-
opentelemetry_endpoint="otl",
2928
opentelemetry_instrumentors=[CustomInstrumentor()],
3029
opentelemetry_log_traces=True,
3130
opentelemetry_generate_health_check_spans=False,
3231
prometheus_metrics_path="/custom-metrics/",
3332
sentry_dsn="https://testdsn@localhost/1",
33+
sentry_additional_params={"transport": SentryTestTransport()},
3434
swagger_offline_docs=True,
3535
)
3636

@@ -75,7 +75,7 @@ async def home() -> str:
7575
test_client.get("/")
7676

7777
stdout = capsys.readouterr().out
78-
assert '"event": "std logger", "level": "info", "logger": "root"' in stdout
78+
assert '"event":"std logger","level":"info","logger":"root"' in stdout
7979
assert stdout.count("std logger") == 1
8080

8181

tests/test_faststream_bootstrap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from starlette.testclient import TestClient
1212

1313
from lite_bootstrap import FastStreamBootstrapper, FastStreamConfig
14-
from tests.conftest import CustomInstrumentor, emulate_package_missing
14+
from tests.conftest import CustomInstrumentor, SentryTestTransport, emulate_package_missing
1515

1616

1717
logger = structlog.getLogger(__name__)
@@ -30,13 +30,13 @@ def build_faststream_config(
3030
service_version="2.0.0",
3131
service_environment="test",
3232
service_debug=False,
33-
opentelemetry_endpoint="otl",
3433
opentelemetry_instrumentors=[CustomInstrumentor()],
3534
opentelemetry_log_traces=True,
3635
opentelemetry_middleware_cls=RedisTelemetryMiddleware,
3736
prometheus_metrics_path="/custom-metrics/",
3837
prometheus_middleware_cls=RedisPrometheusMiddleware,
3938
sentry_dsn="https://testdsn@localhost/1",
39+
sentry_additional_params={"transport": SentryTestTransport()},
4040
health_checks_path="/custom-health/",
4141
logging_buffer_capacity=0,
4242
application=faststream.asgi.AsgiFastStream(

tests/test_free_bootstrap.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from structlog.typing import EventDict
44

55
from lite_bootstrap import FreeBootstrapper, FreeBootstrapperConfig
6-
from tests.conftest import CustomInstrumentor, emulate_package_missing
6+
from tests.conftest import CustomInstrumentor, SentryTestTransport, emulate_package_missing
77

88

99
logger = structlog.getLogger(__name__)
@@ -13,7 +13,6 @@
1313
def free_bootstrapper_config() -> FreeBootstrapperConfig:
1414
return FreeBootstrapperConfig(
1515
service_debug=False,
16-
opentelemetry_endpoint="otl",
1716
opentelemetry_instrumentors=[CustomInstrumentor()],
1817
opentelemetry_log_traces=True,
1918
sentry_dsn="https://testdsn@localhost/1",
@@ -34,10 +33,10 @@ def test_free_bootstrap_logging_not_ready(log_output: list[EventDict]) -> None:
3433
FreeBootstrapper(
3534
bootstrap_config=FreeBootstrapperConfig(
3635
service_debug=True,
37-
opentelemetry_endpoint="otl",
3836
opentelemetry_instrumentors=[CustomInstrumentor()],
3937
opentelemetry_log_traces=True,
4038
sentry_dsn="https://testdsn@localhost/1",
39+
sentry_additional_params={"transport": SentryTestTransport()},
4140
logging_buffer_capacity=0,
4241
),
4342
)

0 commit comments

Comments
 (0)