diff --git a/rclpy/rclpy/client.py b/rclpy/rclpy/client.py index 1dc8a5cf9..1bc0f782b 100644 --- a/rclpy/rclpy/client.py +++ b/rclpy/rclpy/client.py @@ -224,6 +224,12 @@ def service_name(self) -> str: with self.handle: return self.__client.service_name + @property + def logger_name(self) -> str: + """Get the name of the logger associated with the node of the client.""" + with self.handle: + return self.__client.get_logger_name() + def destroy(self) -> None: """ Destroy a container for a ROS service client. diff --git a/rclpy/rclpy/impl/_rclpy_pybind11.pyi b/rclpy/rclpy/impl/_rclpy_pybind11.pyi index 921473814..ed6399e8d 100644 --- a/rclpy/rclpy/impl/_rclpy_pybind11.pyi +++ b/rclpy/rclpy/impl/_rclpy_pybind11.pyi @@ -188,6 +188,9 @@ class Client(Destroyable, Generic[SrvRequestT, SrvResponseT]): ) -> None: """Configure whether introspection is enabled.""" + def get_logger_name(self) -> str: + """Get the name of the logger associated with the node of the client.""" + class Context(Destroyable): @@ -276,6 +279,9 @@ class Service(Destroyable, Generic[SrvRequestT, SrvResponseT]): ) -> None: """Configure whether introspection is enabled.""" + def get_logger_name(self) -> str: + """Get the name of the logger associated with the node of the service.""" + class TypeDescriptionService(Destroyable): diff --git a/rclpy/rclpy/publisher.py b/rclpy/rclpy/publisher.py index 66797f2c4..28990b1cb 100644 --- a/rclpy/rclpy/publisher.py +++ b/rclpy/rclpy/publisher.py @@ -89,6 +89,12 @@ def topic_name(self) -> str: def handle(self) -> '_rclpy.Publisher[MsgT]': return self.__publisher + @property + def logger_name(self) -> str: + """Get the name of the logger associated with the node of the publisher.""" + with self.handle: + return self.__publisher.get_logger_name() + def destroy(self) -> None: """ Destroy a container for a ROS publisher. diff --git a/rclpy/rclpy/service.py b/rclpy/rclpy/service.py index a3115104e..9352f4904 100644 --- a/rclpy/rclpy/service.py +++ b/rclpy/rclpy/service.py @@ -114,6 +114,12 @@ def service_name(self) -> str: with self.handle: return self.__service.name + @property + def logger_name(self) -> str: + """Get the name of the logger associated with the node of the service.""" + with self.handle: + return self.__service.get_logger_name() + def destroy(self) -> None: """ Destroy a container for a ROS service server. diff --git a/rclpy/rclpy/subscription.py b/rclpy/rclpy/subscription.py index 86eedb0a9..3eaf7fc75 100644 --- a/rclpy/rclpy/subscription.py +++ b/rclpy/rclpy/subscription.py @@ -133,6 +133,12 @@ def callback(self, value: Union[Callable[[MsgT], None], 'Subscription.__init__(): callback should be either be callable with one argument' '(to get only the message) or two (to get message and message info)') + @property + def logger_name(self) -> str: + """Get the name of the logger associated with the node of the subscription.""" + with self.handle: + return self.__subscription.get_logger_name() + def __enter__(self) -> 'Subscription[MsgT]': return self diff --git a/rclpy/src/rclpy/client.cpp b/rclpy/src/rclpy/client.cpp index c25a70a55..ccbddfd6a 100644 --- a/rclpy/src/rclpy/client.cpp +++ b/rclpy/src/rclpy/client.cpp @@ -170,6 +170,17 @@ Client::get_service_name() return rcl_client_get_service_name(rcl_client_.get()); } +const char * +Client::get_logger_name() const +{ + const char * node_logger_name = rcl_node_get_logger_name(node_.rcl_ptr()); + if (!node_logger_name) { + throw RCLError("Node logger name not set"); + } + + return node_logger_name; +} + void define_client(py::object module) { @@ -194,6 +205,9 @@ define_client(py::object module) "Take a received response from an earlier request") .def( "configure_introspection", &Client::configure_introspection, - "Configure whether introspection is enabled"); + "Configure whether introspection is enabled") + .def( + "get_logger_name", &Client::get_logger_name, + "Get the name of the logger associated with the node of the client."); } } // namespace rclpy diff --git a/rclpy/src/rclpy/client.hpp b/rclpy/src/rclpy/client.hpp index 56c76f203..4428dfcc4 100644 --- a/rclpy/src/rclpy/client.hpp +++ b/rclpy/src/rclpy/client.hpp @@ -111,6 +111,15 @@ class Client : public Destroyable, public std::enable_shared_from_this const char * get_service_name(); + /// Get the name of the logger associated with the node of the client. + /** + * + * \return logger_name, or + * \return None on failure + */ + const char * + get_logger_name() const; + private: Node node_; std::shared_ptr rcl_client_; diff --git a/rclpy/src/rclpy/service.cpp b/rclpy/src/rclpy/service.cpp index ec3ad1cfe..e24d07b97 100644 --- a/rclpy/src/rclpy/service.cpp +++ b/rclpy/src/rclpy/service.cpp @@ -155,6 +155,17 @@ Service::get_qos_profile() return rclpy::convert_to_qos_dict(&options->qos); } +const char * +Service::get_logger_name() const +{ + const char * node_logger_name = rcl_node_get_logger_name(node_.rcl_ptr()); + if (!node_logger_name) { + throw RCLError("Node logger name not set"); + } + + return node_logger_name; +} + void Service::configure_introspection( Clock & clock, py::object pyqos_service_event_pub, @@ -197,6 +208,9 @@ define_service(py::object module) "Take a request from a given service") .def( "configure_introspection", &Service::configure_introspection, - "Configure whether introspection is enabled"); + "Configure whether introspection is enabled") + .def( + "get_logger_name", &Service::get_logger_name, + "Get the name of the logger associated with the node of the service."); } } // namespace rclpy diff --git a/rclpy/src/rclpy/service.hpp b/rclpy/src/rclpy/service.hpp index 461c9f39f..6b178e18b 100644 --- a/rclpy/src/rclpy/service.hpp +++ b/rclpy/src/rclpy/service.hpp @@ -95,6 +95,15 @@ class Service : public Destroyable, public std::enable_shared_from_this const char * get_service_name(); + /// Get the name of the logger associated with the node of the service. + /** + * + * \return logger_name, or + * \return None on failure + */ + const char * + get_logger_name() const; + /// Get the QoS profile for this service. py::dict get_qos_profile(); diff --git a/rclpy/test/test_client.py b/rclpy/test/test_client.py index c770f6153..bee06ce4c 100644 --- a/rclpy/test/test_client.py +++ b/rclpy/test/test_client.py @@ -264,6 +264,10 @@ def _service(request, response): executor.shutdown() executor_thread.join() + def test_logger_name_is_equal_to_node_name(self) -> None: + with self.node.create_client(GetParameters, 'get/parameters') as cli: + self.assertEqual(cli.logger_name, 'TestClient') + if __name__ == '__main__': unittest.main() diff --git a/rclpy/test/test_publisher.py b/rclpy/test/test_publisher.py index 564bdc848..a5a44c2a9 100644 --- a/rclpy/test/test_publisher.py +++ b/rclpy/test/test_publisher.py @@ -125,6 +125,10 @@ def test_wait_for_all_acked(self) -> None: pub.destroy() sub.destroy() + def test_logger_name_is_equal_to_node_name(self) -> None: + with self.node.create_publisher(BasicTypes, TEST_TOPIC, 10) as pub: + self.assertEqual(pub.logger_name, 'node') + def test_publisher_context_manager() -> None: rclpy.init() diff --git a/rclpy/test/test_service.py b/rclpy/test/test_service.py index bb15e7b34..5d856db06 100644 --- a/rclpy/test/test_service.py +++ b/rclpy/test/test_service.py @@ -20,6 +20,9 @@ from test_msgs.srv import Empty +NODE_NAME = 'test_node' + + @pytest.fixture(autouse=True) def default_context(): rclpy.init() @@ -27,6 +30,23 @@ def default_context(): rclpy.shutdown() +@pytest.fixture +def test_node(): + node = Node(NODE_NAME) + yield node + node.destroy_node() + + +def test_logger_name_is_equal_to_node_name(test_node): + srv = test_node.create_service( + srv_type=Empty, + srv_name='test_srv', + callback=lambda _: None + ) + + assert srv.logger_name == NODE_NAME + + @pytest.mark.parametrize('service_name, namespace, expected', [ # No namespaces ('service', None, '/service'), diff --git a/rclpy/test/test_subscription.py b/rclpy/test/test_subscription.py index 615587daf..3d2d9ccc3 100644 --- a/rclpy/test/test_subscription.py +++ b/rclpy/test/test_subscription.py @@ -23,11 +23,21 @@ from test_msgs.msg import Empty +NODE_NAME = 'test_node' + + @pytest.fixture(scope='session', autouse=True) def setup_ros() -> None: rclpy.init() +@pytest.fixture +def test_node(): + node = Node(NODE_NAME) + yield node + node.destroy_node() + + @pytest.mark.parametrize('topic_name, namespace, expected', [ # No namespaces ('topic', None, '/topic'), @@ -56,6 +66,17 @@ def test_get_subscription_topic_name(topic_name, namespace, expected): node.destroy_node() +def test_logger_name_is_equal_to_node_name(test_node): + sub = test_node.create_subscription( + msg_type=Empty, + topic='topic', + callback=lambda _: None, + qos_profile=10, + ) + + assert sub.logger_name == NODE_NAME + + @pytest.mark.parametrize('topic_name, namespace, cli_args, expected', [ ('topic', None, ['--ros-args', '--remap', 'topic:=new_topic'], '/new_topic'), ('topic', 'ns', ['--ros-args', '--remap', 'topic:=new_topic'], '/ns/new_topic'),