Skip to content

Commit 1acadd9

Browse files
authored
faststream: Fix broker logging (#108)
1 parent be5e973 commit 1acadd9

File tree

4 files changed

+90
-16
lines changed

4 files changed

+90
-16
lines changed

packages/faststream-stomp/faststream_stomp/broker.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import types
33
from collections.abc import Callable, Iterable, Mapping, Sequence
4-
from typing import Any
4+
from typing import Any, Unpack
55

66
import anyio
77
import stompman
@@ -11,11 +11,11 @@
1111
from faststream.broker.types import BrokerMiddleware, CustomCallable
1212
from faststream.log.logging import get_broker_logger
1313
from faststream.security import BaseSecurity
14-
from faststream.types import AnyDict, Decorator, LoggerProto, SendableMessage
14+
from faststream.types import EMPTY, AnyDict, Decorator, LoggerProto, SendableMessage
1515

1616
from faststream_stomp.publisher import StompProducer, StompPublisher
1717
from faststream_stomp.registrator import StompRegistrator
18-
from faststream_stomp.subscriber import StompSubscriber
18+
from faststream_stomp.subscriber import StompLogContext, StompSubscriber
1919

2020

2121
class StompSecurity(BaseSecurity):
@@ -46,7 +46,7 @@ def __init__(
4646
middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]] = (),
4747
graceful_timeout: float | None = 15.0,
4848
# Logging args
49-
logger: LoggerProto | None = None,
49+
logger: LoggerProto | None = EMPTY,
5050
log_level: int = logging.INFO,
5151
log_fmt: str | None = None,
5252
# FastDepends args
@@ -87,10 +87,7 @@ async def start(self) -> None:
8787
await super().start()
8888

8989
for handler in self._subscribers.values():
90-
self._log(
91-
f"`{handler.call_name}` waiting for messages",
92-
extra=handler.get_log_context(None),
93-
)
90+
self._log(f"`{handler.call_name}` waiting for messages", extra=handler.get_log_context(None))
9491
await handler.start()
9592

9693
async def _connect(self, client: stompman.Client) -> stompman.Client: # type: ignore[override]
@@ -125,15 +122,15 @@ async def ping(self, timeout: float | None = None) -> bool:
125122
return False # pragma: no cover
126123

127124
def get_fmt(self) -> str:
125+
# `StompLogContext`
128126
return (
129127
"%(asctime)s %(levelname)-8s - "
130-
f"%(channel)-{self._max_channel_name}s | "
128+
f"%(destination)-{self._max_channel_name}s | "
131129
f"%(message_id)-{self.__max_msg_id_ln}s "
132130
"- %(message)s"
133131
)
134132

135-
def _setup_log_context(self, *, channel: str | None = None) -> None:
136-
self._max_channel_name = max((self._max_channel_name, len(channel or "")))
133+
def _setup_log_context(self, **log_context: Unpack[StompLogContext]) -> None: ... # type: ignore[override]
137134

138135
@property
139136
def _subscriber_setup_extra(self) -> "AnyDict":

packages/faststream-stomp/faststream_stomp/message.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import typing
2-
from typing import cast
1+
from typing import Self, cast
32

43
import stompman
54
from faststream.broker.message import StreamMessage, gen_cor_id
@@ -22,7 +21,7 @@ async def reject(self) -> None:
2221
return await super().reject()
2322

2423
@classmethod
25-
async def from_frame(cls, message: stompman.AckableMessageFrame) -> typing.Self:
24+
async def from_frame(cls, message: stompman.AckableMessageFrame) -> Self:
2625
return cls(
2726
raw_message=message,
2827
body=message.body,

packages/faststream-stomp/faststream_stomp/subscriber.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Callable, Iterable, Sequence
2-
from typing import Any
2+
from typing import Any, TypedDict, cast
33

44
import stompman
55
from fast_depends.dependencies import Depends
@@ -16,6 +16,11 @@
1616
from faststream_stomp.message import StompStreamMessage
1717

1818

19+
class StompLogContext(TypedDict):
20+
destination: str
21+
message_id: str
22+
23+
1924
class StompSubscriber(SubscriberUsecase[stompman.MessageFrame]):
2025
def __init__(
2126
self,
@@ -128,3 +133,10 @@ def get_schema(self) -> dict[str, Channel]:
128133
),
129134
)
130135
}
136+
137+
def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]:
138+
log_context: StompLogContext = {
139+
"destination": message.raw_message.headers["destination"] if message else self.destination,
140+
"message_id": message.message_id if message else "",
141+
}
142+
return cast("dict[str, str]", log_context)

packages/faststream-stomp/test_faststream_stomp/test_integration.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import asyncio
2-
from typing import Annotated
2+
import logging
3+
from dataclasses import dataclass
4+
from typing import TYPE_CHECKING, Annotated
5+
from unittest import mock
36

47
import faker
58
import faststream_stomp
69
import pytest
710
import stompman
811
from faststream import BaseMiddleware, Context, FastStream
912
from faststream.broker.message import gen_cor_id
13+
from faststream.broker.middlewares.logging import CriticalLogMiddleware
1014
from faststream.exceptions import AckMessage, NackMessage, RejectMessage
1115
from faststream_stomp.message import StompStreamMessage
1216

17+
if TYPE_CHECKING:
18+
from faststream_stomp.broker import StompBroker
19+
1320
pytestmark = pytest.mark.anyio
1421

1522

@@ -151,3 +158,62 @@ async def _(message: Annotated[StompStreamMessage, Context()]) -> None:
151158
await broker.start()
152159
await broker.publish(faker.pystr(), destination)
153160
await event.wait()
161+
162+
163+
class TestLogging:
164+
async def test_ok(
165+
self, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest, faker: faker.Faker
166+
) -> None:
167+
monkeypatch.delenv("PYTEST_CURRENT_TEST")
168+
broker: StompBroker = request.getfixturevalue("broker")
169+
assert broker.logger
170+
broker.logger = mock.Mock(log=(log_mock := mock.Mock()))
171+
172+
@broker.subscriber(destination := faker.pystr())
173+
def some_handler() -> None: ...
174+
175+
async with broker:
176+
await broker.start()
177+
178+
assert log_mock.mock_calls == [
179+
mock.call(
180+
logging.INFO,
181+
"`SomeHandler` waiting for messages",
182+
extra={"destination": destination, "message_id": ""},
183+
exc_info=None,
184+
)
185+
]
186+
187+
async def test_raises(
188+
self, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest, faker: faker.Faker
189+
) -> None:
190+
monkeypatch.delenv("PYTEST_CURRENT_TEST")
191+
broker: StompBroker = request.getfixturevalue("broker")
192+
assert isinstance(broker._middlewares[0], CriticalLogMiddleware)
193+
assert broker._middlewares[0].logger
194+
broker._middlewares[0].logger = mock.Mock(log=(log_mock := mock.Mock()))
195+
event = asyncio.Event()
196+
message_id: str | None = None
197+
198+
@dataclass
199+
class MyError(Exception): ...
200+
201+
@broker.subscriber(destination := faker.pystr())
202+
def some_handler(message_frame: Annotated[stompman.MessageFrame, Context("message.raw_message")]) -> None:
203+
nonlocal message_id
204+
message_id = message_frame.headers["message-id"]
205+
event.set()
206+
raise MyError
207+
208+
async with broker:
209+
await broker.start()
210+
await broker.publish(faker.pystr(), destination)
211+
await event.wait()
212+
213+
assert message_id
214+
extra = {"destination": destination, "message_id": message_id}
215+
assert log_mock.mock_calls == [
216+
mock.call(logging.INFO, "Received", extra=extra),
217+
mock.call(logging.ERROR, "MyError: ", extra=extra, exc_info=MyError()),
218+
mock.call(logging.INFO, "Processed", extra=extra),
219+
]

0 commit comments

Comments
 (0)