Skip to content

Commit 21a0f16

Browse files
Logger for UDS Communication (#434)
UDS communication logger introduced. Documentation and examples added.
1 parent 77def74 commit 21a0f16

15 files changed

Lines changed: 955 additions & 22 deletions

File tree

docs/source/pages/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and configurations.
88
.. toctree::
99

1010
examples/client.rst
11+
examples/transport.rst
1112
examples/CAN/can.rst
1213

1314
.. seealso:: https://github.com/mdabrowski1990/uds/tree/main/examples
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Transport
2+
=========
3+
Examples how to use features related to Transport layer.
4+
5+
.. seealso:: https://github.com/mdabrowski1990/uds/tree/main/examples/transport
6+
7+
8+
.. _example-transport-logger-instance:
9+
10+
Setup logger to Transport Interface instance
11+
--------------------------------------------
12+
Example how to configure and activate Transport Logger for Transport Interface instance.
13+
14+
.. include:: ../../../../examples/transport/instance_logger.py
15+
:code: python
16+
17+
18+
.. _example-transport-logger-class:
19+
20+
Setup logger to Transport Interface class
21+
-----------------------------------------
22+
Example how to configure and activate Transport Logger for Transport Interface class.
23+
24+
.. include:: ../../../../examples/transport/class_logger.py
25+
:code: python

docs/source/pages/user_guide.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ for getting started.
1111
user_guide/addressing.rst
1212
user_guide/client.rst
1313
user_guide/message_translation.rst
14+
user_guide/logging.rst
1415
user_guide/can.rst
1516
user_guide/custom.rst
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
Communication Logging
2+
=====================
3+
UDS communication logging utilities are provided in :mod:`uds.transport_interface.logger` module.
4+
5+
6+
TransportLogger
7+
---------------
8+
:class:`~uds.transport_interface.logger.TransportLogger` is the recommended implementation
9+
of the communication logging feature.
10+
The logger works by wrapping transport interface methods responsible for sending and receiving
11+
:ref:`UDS Messages <knowledge-base-diagnostic-message>` and :ref:`Packets <knowledge-base-packet>`.
12+
13+
This logger integrates with the built-in
14+
`logging <https://docs.python.org/3/library/logging.html>`_ module.
15+
16+
Thanks to the modular package design, custom logging implementations can easily be inserted
17+
between existing communication layers.
18+
19+
.. seealso:: `Python logging documentation <https://docs.python.org/3/library/logging.html>`_
20+
21+
Attributes:
22+
23+
- :attr:`~uds.transport_interface.logger.TransportLogger.logger`
24+
- :attr:`~uds.transport_interface.logger.TransportLogger.message_logging_level`
25+
- :attr:`~uds.transport_interface.logger.TransportLogger.packet_logging_level`
26+
- :attr:`~uds.transport_interface.logger.TransportLogger.log_sending`
27+
- :attr:`~uds.transport_interface.logger.TransportLogger.log_receiving`
28+
- :attr:`~uds.transport_interface.logger.TransportLogger.message_log_format`
29+
- :attr:`~uds.transport_interface.logger.TransportLogger.packet_log_format`
30+
31+
32+
Configuration
33+
`````````````
34+
35+
Upon :class:`~uds.transport_interface.logger.TransportLogger` object creation, the user can configure how the logger
36+
behaves during the communication.
37+
38+
**Example code:**
39+
40+
.. code-block:: python
41+
42+
import logging
43+
from uds.transport_interface import TransportLogger
44+
45+
# create example Transport Logger
46+
transport_logger = TransportLogger(
47+
logger_name="UDS",
48+
message_logging_level=logging.INFO,
49+
packet_logging_level=logging.DEBUG,
50+
log_sending=True,
51+
log_receiving=True,
52+
message_log_format="{record.direction.name} {record.addressing_type.name} {record.payload}",
53+
packet_log_format="{record.direction.name} {record}")
54+
55+
transport_logger.log_sending = False # do not log outgoing communication
56+
transport_logger.packet_logging_level = None # do not log packets
57+
58+
59+
Activation
60+
``````````
61+
There are two ways to activate the logger:
62+
63+
- `Decorating Transport Interface class`_
64+
- `Decorating Transport Interface instance`_
65+
66+
67+
Decorating Transport Interface class
68+
''''''''''''''''''''''''''''''''''''
69+
Transport Logger activation is possible by decorating Transport Interface class.
70+
This is the recommended (and most Pythonic) approach when implementing custom transport interfaces.
71+
72+
**Example code:**
73+
74+
.. code-block:: python
75+
76+
from uds.transport_interface import AbstractTransportInterface, TransportLogger
77+
78+
# let's assume that we have `transport_logger` already configured
79+
transport_logger: TransportLogger
80+
81+
@transport_logger
82+
class MyTransportInterface(AbstractTransportInterface):
83+
... # TODO: custom implementation
84+
85+
86+
It is also possible to decorate existing Transport Interfaces.
87+
88+
**Example code:**
89+
90+
.. code-block:: python
91+
92+
from uds.transport_interface import TransportLogger
93+
from uds.can import PyCanTransportInterface
94+
95+
# let's assume that we have `transport_logger` already configured
96+
transport_logger: TransportLogger
97+
98+
PyCanTransportInterfaceWithLogging = transport_logger(PyCanTransportInterface)
99+
100+
.. seealso:: :ref:`An example script <example-transport-logger-class>`
101+
102+
103+
Decorating Transport Interface instance
104+
'''''''''''''''''''''''''''''''''''''''
105+
Another option is to decorate an already existing transport interface instance.
106+
107+
**Example code:**
108+
109+
.. code-block:: python
110+
111+
from uds.transport_interface import AbstractTransportInterface, TransportLogger
112+
113+
# let's assume that we have `transport_interface` already configured
114+
transport_interface: AbstractTransportInterface
115+
116+
# let's assume that we have `transport_logger` already configured
117+
transport_logger: TransportLogger
118+
119+
# add logging to the transport_interface
120+
transport_interface_with_logger = transport_logger(transport_interface)
121+
122+
.. seealso:: :ref:`An example script <example-transport-logger-instance>`
123+
124+
125+
Customization
126+
`````````````
127+
The easiest way to create your own transport logger is to inherit after
128+
:class:`~uds.transport_interface.logger.TransportLogger` class and add your own features.
129+
This is also the easiest way to define more advanced or custom logging messages.
130+
131+
**Example code:**
132+
133+
.. code-block:: python
134+
135+
from uds.transport_interface import TransportLogger
136+
from uds.addressing import TransmissionDirection
137+
from uds.message import UdsMessageRecord
138+
from uds.utilities import bytes_to_hex
139+
140+
class MyTransportLogger(TransportLogger):
141+
142+
def log_message(self, record: UdsMessageRecord) -> None:
143+
"""Log a message after receiving/transmitting UDS Message."""
144+
if self.message_logging_level is not None:
145+
if record.direction == TransmissionDirection.TRANSMITTED:
146+
message = f"Transmitted message with payload: {bytes_to_hex(record.payload)}"
147+
else:
148+
message = f"Received message with payload: {bytes_to_hex(record.payload)}"
149+
self.logger.log(level=self.message_logging_level,
150+
msg=message)

examples/debugging/Kvaser/python_can_timing_issue.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@
1515
message = Message(data=[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], arbitration_id=0x100)
1616

1717
for _ in range(100):
18-
timestamp_before_send = time()
18+
time_before_send = time()
1919
Timer(interval=0.1, function=kvaser_interface_1.send, args=(message,)).start()
2020

2121
sent_message = buffered_reader.get_message(timeout=1)
22-
timestamp_after_send = time()
22+
time_after_send = time()
2323

2424
print(f"-----------------------------------------------\n"
2525
f"Result:\n"
26-
f"Timestamp before send: {timestamp_before_send}\n"
26+
f"Timestamp before send: {time_before_send}\n"
2727
f"Message timestamp: {sent_message.timestamp}\n"
28-
f"Current timestamp: {timestamp_after_send}\n"
29-
f"Timestamp before send <= Message timestamp <= Current timestamp: {timestamp_before_send <= sent_message.timestamp <= timestamp_after_send} (expected `True`)\n"
30-
f"Current timestamp - Message timestamp: {timestamp_after_send - sent_message.timestamp:06f} (excepted >= 0)")
28+
f"Current timestamp: {time_after_send}\n"
29+
f"Timestamp before send <= Message timestamp <= Current timestamp: {time_before_send <= sent_message.timestamp <= time_after_send} (expected `True`)\n"
30+
f"Current timestamp - Message timestamp: {time_after_send - sent_message.timestamp:06f} (excepted >= 0)")
3131

3232
kvaser_interface_1.shutdown()
3333
kvaser_interface_2.shutdown()

examples/debugging/Kvaser/python_can_timing_issue_2.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@
1414
message = Message(data=[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], arbitration_id=0x100)
1515

1616
for _ in range(100):
17-
timestamp_before_send = time()
17+
time_before_send = time()
1818
kvaser_interface_1.send(message)
1919
sent_message = buffered_reader.get_message(timeout=1)
20-
timestamp_after_send = time()
20+
time_after_send = time()
2121

2222
print(f"-----------------------------------------------\n"
2323
f"Result:\n"
24-
f"Timestamp before send: {timestamp_before_send}\n"
24+
f"Timestamp before send: {time_before_send}\n"
2525
f"Message timestamp: {sent_message.timestamp}\n"
26-
f"Current timestamp: {timestamp_after_send}\n"
27-
f"Message timestamp - Timestamp before send: {sent_message.timestamp - timestamp_before_send:06f} (expected > 0)\n"
28-
f"Current timestamp - Message timestamp: {timestamp_after_send - sent_message.timestamp:06f} (expected > 0)\n"
29-
f"Timestamp before send <= Message timestamp <= Current timestamp: {timestamp_before_send <= sent_message.timestamp <= timestamp_after_send} (expected `True`)")
26+
f"Current timestamp: {time_after_send}\n"
27+
f"Message timestamp - Timestamp before send: {sent_message.timestamp - time_before_send:06f} (expected > 0)\n"
28+
f"Current timestamp - Message timestamp: {time_after_send - sent_message.timestamp:06f} (expected > 0)\n"
29+
f"Timestamp before send <= Message timestamp <= Current timestamp: {time_before_send <= sent_message.timestamp <= time_after_send} (expected `True`)")
3030

3131
kvaser_interface_1.shutdown()
3232
kvaser_interface_2.shutdown()

examples/transport/class_logger.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Configure logger for class of Transport Interface."""
2+
3+
import logging
4+
5+
from uds.transport_interface import AbstractTransportInterface, TransportLogger
6+
7+
# configure your logging
8+
# https://docs.python.org/3/library/logging.html
9+
logger = logging.getLogger("UDS") # example logger name
10+
logger.setLevel(logging.DEBUG) # example logging level
11+
# optionally configure logging to file
12+
file_handler = logging.FileHandler(filename="uds.log", encoding="utf-8")
13+
file_handler.setLevel(logging.DEBUG)
14+
logger.addHandler(file_handler)
15+
# optionally configure logging to python console
16+
stream_handler = logging.StreamHandler()
17+
stream_handler.setLevel(logging.INFO)
18+
logger.addHandler(stream_handler)
19+
20+
# configure your logger
21+
# https://uds.readthedocs.io/en/stable/pages/user_guide/logging.html#configuration
22+
transport_logger = TransportLogger(logger_name="UDS", # the same name as previously configured logger
23+
message_logging_level=logging.INFO,
24+
packet_logging_level=logging.DEBUG,
25+
log_sending=True,
26+
log_receiving=True)
27+
28+
# activate your logger
29+
# https://uds.readthedocs.io/en/stable/pages/user_guide/logging.html#decorating-transport-interface-class
30+
@transport_logger
31+
class CustomTransportInterface(AbstractTransportInterface):
32+
... # TODO: implement your interface class
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Configure logger for instance of Transport Interface."""
2+
3+
import logging
4+
5+
from uds.transport_interface import AbstractTransportInterface, TransportLogger
6+
7+
# configure your own Transport Interface
8+
# https://uds.readthedocs.io/en/stable/pages/user_guide/quickstart.html#create-transport-interface
9+
transport_interface: AbstractTransportInterface = ... # TODO: provide your implementation here
10+
11+
# configure your logging
12+
# https://docs.python.org/3/library/logging.html
13+
logger = logging.getLogger("UDS") # example logger name
14+
logger.setLevel(logging.DEBUG) # example logging level
15+
# optionally configure logging to file
16+
file_handler = logging.FileHandler(filename="uds.log", encoding="utf-8")
17+
file_handler.setLevel(logging.DEBUG)
18+
logger.addHandler(file_handler)
19+
# optionally configure logging to python console
20+
stream_handler = logging.StreamHandler()
21+
stream_handler.setLevel(logging.INFO)
22+
logger.addHandler(stream_handler)
23+
24+
# configure your logger
25+
# https://uds.readthedocs.io/en/stable/pages/user_guide/logging.html#configuration
26+
transport_logger = TransportLogger(logger_name="UDS", # the same name as previously configured logger
27+
message_logging_level=logging.INFO,
28+
packet_logging_level=logging.DEBUG,
29+
log_sending=True,
30+
log_receiving=True)
31+
32+
# activate your logger
33+
# https://uds.readthedocs.io/en/stable/pages/user_guide/logging.html#decorating-transport-interface-instance
34+
transport_interface_with_logger = transport_logger(transport_interface)
35+
36+
# TODO: use `transport_interface_with_logger` the same way as `transport_interface`

tests/software_tests/can/transport_interface/test_python_can.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ def test_teardown_sync_listening__notifier(self):
545545
assert PyCanTransportInterface._PyCanTransportInterface__teardown_sync_listening(
546546
self.mock_can_transport_interface) is None
547547
assert self.mock_can_transport_interface.notifier is None
548-
mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT)
548+
mock_notifier.stop.assert_called_once_with()
549549
self.mock_warn.assert_called_once()
550550

551551
def test_teardown_sync_listening__notifier_with_suppressed_warning(self):
@@ -554,7 +554,7 @@ def test_teardown_sync_listening__notifier_with_suppressed_warning(self):
554554
assert PyCanTransportInterface._PyCanTransportInterface__teardown_sync_listening(
555555
self.mock_can_transport_interface, suppress_warning=True) is None
556556
assert self.mock_can_transport_interface.notifier is None
557-
mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT)
557+
mock_notifier.stop.assert_called_once_with()
558558
self.mock_warn.assert_not_called()
559559

560560
# __teardown_async_listening
@@ -572,7 +572,7 @@ def test_teardown_async_listening__notifier(self):
572572
assert PyCanTransportInterface._PyCanTransportInterface__teardown_async_listening(
573573
self.mock_can_transport_interface) is None
574574
assert self.mock_can_transport_interface.async_notifier is None
575-
mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT)
575+
mock_notifier.stop.assert_called_once_with()
576576
self.mock_warn.assert_called_once()
577577

578578
def test_teardown_async_listening__notifier_with_suppressed_warning(self):
@@ -581,7 +581,7 @@ def test_teardown_async_listening__notifier_with_suppressed_warning(self):
581581
assert PyCanTransportInterface._PyCanTransportInterface__teardown_async_listening(
582582
self.mock_can_transport_interface, suppress_warning=True) is None
583583
assert self.mock_can_transport_interface.async_notifier is None
584-
mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT)
584+
mock_notifier.stop.assert_called_once_with()
585585
self.mock_warn.assert_not_called()
586586

587587
# __validate_timeout

0 commit comments

Comments
 (0)