From 4cae644df7c9a957a8caad2852395d82daa6678d Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Fri, 23 Oct 2026 10:50:43 +0200 Subject: [PATCH 1/2] refactor(gateway): log the HTTP layer through a ROS-free logging interface Route the HTTP server, handlers, aggregation, plugins and script provider through a logging interface that does not depend on RCLCPP_* macros. The call site is passed to the log sink. The logic behind this layer can then be built and tested without ROS and without DDS setup. --- src/ros2_medkit_gateway/CMakeLists.txt | 1 + .../aggregation/aggregation_manager.hpp | 8 +- .../ros2_medkit_gateway/core/logging.hpp | 90 +++++++ .../http/handlers/handler_context.hpp | 7 +- .../ros2/ros2_log_sink.hpp | 33 +++ .../src/aggregation/aggregation_manager.cpp | 49 ++-- src/ros2_medkit_gateway/src/core/logging.cpp | 99 ++++++++ .../src/default_script_provider.cpp | 9 +- src/ros2_medkit_gateway/src/gateway_node.cpp | 6 +- .../src/http/fan_out_helpers.cpp | 7 +- .../src/http/handlers/auth_handlers.cpp | 7 +- .../handlers/cyclic_subscription_handlers.cpp | 4 +- .../src/http/handlers/data_handlers.cpp | 51 ++-- .../src/http/handlers/discovery_handlers.cpp | 25 +- .../src/http/handlers/fault_handlers.cpp | 67 ++--- .../src/http/handlers/health_handlers.cpp | 11 +- .../src/http/handlers/lifecycle_handlers.cpp | 21 +- .../src/http/handlers/lock_handlers.cpp | 3 +- .../src/http/handlers/operation_handlers.cpp | 44 ++-- .../src/http/handlers/sse_fault_handler.cpp | 57 ++--- .../http/handlers/sse_transport_provider.cpp | 20 +- .../src/http/handlers/trigger_handlers.cpp | 13 +- .../src/http/handlers/update_handlers.cpp | 3 +- .../src/http/http_server.cpp | 27 +-- .../src/http/rest_server.cpp | 69 +++--- .../src/openapi/capability_generator.cpp | 17 +- .../src/plugins/plugin_loader.cpp | 64 +++-- .../src/plugins/plugin_manager.cpp | 103 ++++---- .../src/ros2/ros2_log_sink.cpp | 33 +++ .../src/script_manager.cpp | 71 +++--- .../test/demo_nodes/test_gateway_plugin.cpp | 2 + .../test/test_handler_context.cpp | 5 +- .../CMakeLists.txt | 1 + .../features/test_rest_logger_names.test.py | 228 ++++++++++++++++++ 34 files changed, 879 insertions(+), 376 deletions(-) create mode 100644 src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp create mode 100644 src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp create mode 100644 src/ros2_medkit_gateway/src/core/logging.cpp create mode 100644 src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp create mode 100644 src/ros2_medkit_integration_tests/test/features/test_rest_logger_names.test.py diff --git a/src/ros2_medkit_gateway/CMakeLists.txt b/src/ros2_medkit_gateway/CMakeLists.txt index 511810d69..eb6dc3f88 100644 --- a/src/ros2_medkit_gateway/CMakeLists.txt +++ b/src/ros2_medkit_gateway/CMakeLists.txt @@ -166,6 +166,7 @@ add_library(gateway_ros2 STATIC src/ros2/transports/ros2_topic_subscription_transport.cpp src/ros2/transports/ros2_topic_transport.cpp src/ros2/status/ros2_lifecycle_state_reader.cpp + src/ros2/ros2_log_sink.cpp src/ros2/trigger_topic_subscriber.cpp src/default_script_provider.cpp src/discovery/discovery_manager.cpp diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/aggregation/aggregation_manager.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/aggregation/aggregation_manager.hpp index 26ba9cc75..8db3ed2be 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/aggregation/aggregation_manager.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/aggregation/aggregation_manager.hpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -28,6 +27,7 @@ #include "ros2_medkit_gateway/core/aggregation/classification.hpp" #include "ros2_medkit_gateway/core/aggregation/peer_client.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include "ros2_medkit_gateway/dto/aggregation.hpp" namespace ros2_medkit_gateway { @@ -174,7 +174,7 @@ class AggregationManager { * @param config Aggregation configuration * @param logger Optional logger for TLS warnings (pass nullptr to suppress) */ - explicit AggregationManager(const AggregationConfig & config, rclcpp::Logger * logger = nullptr); + explicit AggregationManager(const AggregationConfig & config, const log::Logger * logger = nullptr); /** * @brief Cancel every in-flight HTTP call to every peer. @@ -258,7 +258,7 @@ class AggregationManager { MergedPeerResult fetch_and_merge_peer_entities(const std::vector & local_areas, const std::vector & local_components, const std::vector & local_apps, const std::vector & local_functions, - size_t max_entities_per_peer = 10000, rclcpp::Logger * logger = nullptr); + size_t max_entities_per_peer = 10000, const log::Logger * logger = nullptr); /** * @brief Update the routing table (entity_id -> peer_name) @@ -405,7 +405,7 @@ class AggregationManager { private: AggregationConfig config_; - rclcpp::Logger logger_; + log::Logger logger_; size_t static_peer_count_{0}; ///< Number of statically configured peers (not subject to max_discovered_peers) /// Whether a peer whose picture could not be refreshed can still be reached. /// diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp new file mode 100644 index 000000000..b2ed4ab9a --- /dev/null +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp @@ -0,0 +1,90 @@ +// Copyright 2026 bburda +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +namespace ros2_medkit_gateway::log { + +/// Severity values match the rcutils severities. +enum class Level { kDebug = 10, kInfo = 20, kWarn = 30, kError = 40, kFatal = 50 }; + +/// Call site of a log line. +struct Location { + const char * file; + const char * function; + int line; +}; + +/// Destination of all log lines. Must be thread-safe. +class Sink { + public: + Sink() = default; + Sink(const Sink &) = delete; + Sink & operator=(const Sink &) = delete; + Sink(Sink &&) = delete; + Sink & operator=(Sink &&) = delete; + virtual ~Sink() = default; + + virtual bool enabled(std::string_view logger, Level level) const = 0; + virtual void write(std::string_view logger, Level level, std::string_view message, const Location & location) = 0; +}; + +/// Replaces the active sink. Call it once at startup, before other threads log. +/// A null sink restores the default sink, which writes "[LEVEL] [name]: message" +/// to stderr for INFO and above. +void set_sink(std::shared_ptr sink); + +/// A named log source. Cheap to copy. +class Logger { + public: + explicit Logger(std::string name); + const std::string & name() const { + return name_; + } + bool enabled(Level level) const; + + private: + std::string name_; +}; + +namespace detail { +/// Process-wide sink slot. Default visibility, so a plugin that links this code +/// binds to the executable's slot. +__attribute__((visibility("default"))) std::shared_ptr & sink_slot(); + +void emit(const Logger & logger, Level level, const Location & location, const char * fmt, ...) + __attribute__((format(printf, 4, 5))); +} // namespace detail + +} // namespace ros2_medkit_gateway::log + +// The level is checked before the arguments are evaluated or formatted. +#define MEDKIT_LOG_AT(logger, level, ...) \ + do { \ + const auto & medkit_log_ref_ = (logger); \ + if (medkit_log_ref_.enabled(level)) { \ + ::ros2_medkit_gateway::log::detail::emit( \ + medkit_log_ref_, level, ::ros2_medkit_gateway::log::Location{__FILE__, __func__, __LINE__}, __VA_ARGS__); \ + } \ + } while (0) + +#define MEDKIT_LOG_DEBUG(logger, ...) MEDKIT_LOG_AT(logger, ::ros2_medkit_gateway::log::Level::kDebug, __VA_ARGS__) +#define MEDKIT_LOG_INFO(logger, ...) MEDKIT_LOG_AT(logger, ::ros2_medkit_gateway::log::Level::kInfo, __VA_ARGS__) +#define MEDKIT_LOG_WARN(logger, ...) MEDKIT_LOG_AT(logger, ::ros2_medkit_gateway::log::Level::kWarn, __VA_ARGS__) +#define MEDKIT_LOG_ERROR(logger, ...) MEDKIT_LOG_AT(logger, ::ros2_medkit_gateway::log::Level::kError, __VA_ARGS__) +#define MEDKIT_LOG_FATAL(logger, ...) MEDKIT_LOG_AT(logger, ::ros2_medkit_gateway::log::Level::kFatal, __VA_ARGS__) diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/handlers/handler_context.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/handlers/handler_context.hpp index 030b24c90..20e69af28 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/handlers/handler_context.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/handlers/handler_context.hpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -32,6 +31,7 @@ #include "ros2_medkit_gateway/core/config.hpp" #include "ros2_medkit_gateway/core/http/error_codes.hpp" #include "ros2_medkit_gateway/core/http/http_utils.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include "ros2_medkit_gateway/core/models/entity_capabilities.hpp" #include "ros2_medkit_gateway/core/models/entity_types.hpp" #include "ros2_medkit_gateway/core/models/error_info.hpp" @@ -354,8 +354,9 @@ class HandlerContext { /** * @brief Get logger for handlers */ - static rclcpp::Logger logger() { - return rclcpp::get_logger("rest_server"); + static const log::Logger & logger() { + static const log::Logger instance("rest_server"); + return instance; } /** diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp new file mode 100644 index 000000000..834ab3d3e --- /dev/null +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp @@ -0,0 +1,33 @@ +// Copyright 2026 bburda +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "ros2_medkit_gateway/core/logging.hpp" + +namespace ros2_medkit_gateway { + +/// Forwards log lines to rcutils under the same logger name, so per-name level +/// control (`--log-level :=debug`) and `/rosout` behave as with rclcpp. +class Ros2LogSink : public log::Sink { + public: + bool enabled(std::string_view logger, log::Level level) const override; + void write(std::string_view logger, log::Level level, std::string_view message, + const log::Location & location) override; +}; + +} // namespace ros2_medkit_gateway diff --git a/src/ros2_medkit_gateway/src/aggregation/aggregation_manager.cpp b/src/ros2_medkit_gateway/src/aggregation/aggregation_manager.cpp index e4b60052b..c7d198085 100644 --- a/src/ros2_medkit_gateway/src/aggregation/aggregation_manager.cpp +++ b/src/ros2_medkit_gateway/src/aggregation/aggregation_manager.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/aggregation/aggregation_manager.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -24,8 +25,6 @@ #include #include -#include - #include "ros2_medkit_gateway/core/aggregation/entity_merger.hpp" #include "ros2_medkit_gateway/core/http/error_codes.hpp" @@ -189,8 +188,8 @@ bool is_valid_peer_url(const std::string & url) { } // namespace -AggregationManager::AggregationManager(const AggregationConfig & config, rclcpp::Logger * logger) - : config_(config), logger_(logger ? *logger : rclcpp::get_logger("aggregation_manager")) { +AggregationManager::AggregationManager(const AggregationConfig & config, const log::Logger * logger) + : config_(config), logger_(logger ? *logger : log::Logger("aggregation_manager")) { for (const auto & peer_cfg : config_.peers) { // Validate scheme for static peers (http/https only). // Unlike mDNS peers, loopback addresses are valid for static config @@ -204,14 +203,15 @@ AggregationManager::AggregationManager(const AggregationConfig & config, rclcpp: if (peer_cfg.url.rfind("http://", 0) == 0) { if (config_.require_tls) { if (logger) { - RCLCPP_ERROR(*logger, "Aggregation: skipping peer '%s' at %s - require_tls is enabled but URL uses http://", - peer_cfg.name.c_str(), peer_cfg.url.c_str()); + MEDKIT_LOG_ERROR(*logger, + "Aggregation: skipping peer '%s' at %s - require_tls is enabled but URL uses http://", + peer_cfg.name.c_str(), peer_cfg.url.c_str()); } continue; } if (logger) { - RCLCPP_WARN(*logger, "Aggregation: peer '%s' at %s uses cleartext HTTP - consider using HTTPS", - peer_cfg.name.c_str(), peer_cfg.url.c_str()); + MEDKIT_LOG_WARN(*logger, "Aggregation: peer '%s' at %s uses cleartext HTTP - consider using HTTPS", + peer_cfg.name.c_str(), peer_cfg.url.c_str()); } } @@ -265,10 +265,10 @@ void AggregationManager::add_discovered_peer(const std::string & url, const std: // Enforce max_discovered_peers limit (static peers do not count) size_t discovered_count = peers_.size() - static_peer_count_; if (discovered_count >= config_.max_discovered_peers) { - RCLCPP_WARN(logger_, - "Aggregation: max_discovered_peers limit (%zu) reached, " - "rejecting peer '%s' at %s", - config_.max_discovered_peers, name.c_str(), url.c_str()); + MEDKIT_LOG_WARN(logger_, + "Aggregation: max_discovered_peers limit (%zu) reached, " + "rejecting peer '%s' at %s", + config_.max_discovered_peers, name.c_str(), url.c_str()); return; } @@ -424,7 +424,7 @@ PeerEntities AggregationManager::replay_declaration(const std::string & peer_nam AggregationManager::MergedPeerResult AggregationManager::fetch_and_merge_peer_entities( const std::vector & local_areas, const std::vector & local_components, const std::vector & local_apps, const std::vector & local_functions, size_t max_entities_per_peer, - rclcpp::Logger * logger) { + const log::Logger * logger) { MergedPeerResult merged; merged.areas = local_areas; merged.components = local_components; @@ -535,7 +535,8 @@ AggregationManager::MergedPeerResult AggregationManager::fetch_and_merge_peer_en // would outlive the cycle that failed and be replayed as the peer's own // account of itself. if (logger) { - RCLCPP_WARN(*logger, "Failed to fetch entities from peer '%s': %s", pfr.peer_name.c_str(), pfr.error.c_str()); + MEDKIT_LOG_WARN(*logger, "Failed to fetch entities from peer '%s': %s", pfr.peer_name.c_str(), + pfr.error.c_str()); } unread_peers.push_back({pfr.peer_name, pfr.reachability, pfr.reachability == Reachability::kReachable ? "refresh incomplete" : "not answering"}); @@ -546,8 +547,8 @@ AggregationManager::MergedPeerResult AggregationManager::fetch_and_merge_peer_en for (const auto & route : pfr.entities.absent_routes) { routes += routes.empty() ? route : ", " + route; } - RCLCPP_WARN(*logger, "Peer '%s' does not expose %s; aggregating without the members those routes carry", - pfr.peer_name.c_str(), routes.c_str()); + MEDKIT_LOG_WARN(*logger, "Peer '%s' does not expose %s; aggregating without the members those routes carry", + pfr.peer_name.c_str(), routes.c_str()); } remember_declaration(pfr.peer_name, pfr.entities); to_merge.push_back(std::move(pfr)); @@ -565,11 +566,11 @@ AggregationManager::MergedPeerResult AggregationManager::fetch_and_merge_peer_en const size_t count = retained.areas.size() + retained.components.size() + retained.apps.size() + retained.functions.size(); if (peer.reachability == Reachability::kUnreachable) { - RCLCPP_INFO(*logger, "Peer '%s' %s; retaining %zu declared entities as unavailable", peer.name.c_str(), - peer.reason.c_str(), count); + MEDKIT_LOG_INFO(*logger, "Peer '%s' %s; retaining %zu declared entities as unavailable", peer.name.c_str(), + peer.reason.c_str(), count); } else { - RCLCPP_WARN(*logger, "Peer '%s' %s; it is still reachable, so its %zu declared entities stand as last read", - peer.name.c_str(), peer.reason.c_str(), count); + MEDKIT_LOG_WARN(*logger, "Peer '%s' %s; it is still reachable, so its %zu declared entities stand as last read", + peer.name.c_str(), peer.reason.c_str(), count); } } PeerFetchResult replayed; @@ -626,7 +627,7 @@ AggregationManager::MergedPeerResult AggregationManager::fetch_and_merge_peer_en // Log collision-prefixed entities to help operators diagnose naming conflicts if (id.find(EntityMerger::SEPARATOR) != std::string::npos) { if (logger) { - RCLCPP_WARN(*logger, "Entity ID collision: '%s' prefixed for peer '%s'", id.c_str(), name.c_str()); + MEDKIT_LOG_WARN(*logger, "Entity ID collision: '%s' prefixed for peer '%s'", id.c_str(), name.c_str()); } } } @@ -657,7 +658,7 @@ AggregationManager::MergedPeerResult AggregationManager::fetch_and_merge_peer_en if (logger) { for (const auto & msg : classified.malformed_parent_warnings) { - RCLCPP_WARN(*logger, "Aggregation: %s", msg.c_str()); + MEDKIT_LOG_WARN(*logger, "Aggregation: %s", msg.c_str()); } for (const auto & w : merged.leaf_warnings) { std::string peers_str; @@ -667,8 +668,8 @@ AggregationManager::MergedPeerResult AggregationManager::fetch_and_merge_peer_en } peers_str += w.peer_names[i]; } - RCLCPP_WARN(*logger, "Leaf Component '%s' announced by multiple peers: %s. Routing uses last-writer-wins.", - w.entity_id.c_str(), peers_str.c_str()); + MEDKIT_LOG_WARN(*logger, "Leaf Component '%s' announced by multiple peers: %s. Routing uses last-writer-wins.", + w.entity_id.c_str(), peers_str.c_str()); } } diff --git a/src/ros2_medkit_gateway/src/core/logging.cpp b/src/ros2_medkit_gateway/src/core/logging.cpp new file mode 100644 index 000000000..6a5071ff1 --- /dev/null +++ b/src/ros2_medkit_gateway/src/core/logging.cpp @@ -0,0 +1,99 @@ +// Copyright 2026 bburda +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ros2_medkit_gateway/core/logging.hpp" + +#include +#include +#include +#include +#include + +namespace ros2_medkit_gateway::log { + +namespace { + +const char * level_name(Level level) { + switch (level) { + case Level::kDebug: + return "DEBUG"; + case Level::kInfo: + return "INFO"; + case Level::kWarn: + return "WARN"; + case Level::kError: + return "ERROR"; + case Level::kFatal: + return "FATAL"; + } + return "UNKNOWN"; +} + +class StderrSink : public Sink { + public: + bool enabled(std::string_view /*logger*/, Level level) const override { + return level >= Level::kInfo; + } + void write(std::string_view logger, Level level, std::string_view message, const Location & /*location*/) override { + std::fprintf(stderr, "[%s] [%.*s]: %.*s\n", level_name(level), static_cast(logger.size()), logger.data(), + static_cast(message.size()), message.data()); + } +}; + +std::shared_ptr current_sink() { + return std::atomic_load(&detail::sink_slot()); +} + +} // namespace + +void set_sink(std::shared_ptr sink) { + if (!sink) { + sink = std::make_shared(); + } + std::atomic_store(&detail::sink_slot(), std::move(sink)); +} + +Logger::Logger(std::string name) : name_(std::move(name)) { +} + +bool Logger::enabled(Level level) const { + return current_sink()->enabled(name_, level); +} + +namespace detail { + +std::shared_ptr & sink_slot() { + static std::shared_ptr slot = std::make_shared(); + return slot; +} + +void emit(const Logger & logger, Level level, const Location & location, const char * fmt, ...) { + std::va_list args; + va_start(args, fmt); + std::va_list copy; + va_copy(copy, args); + const int needed = std::vsnprintf(nullptr, 0, fmt, copy); + va_end(copy); + std::vector buffer(needed > 0 ? static_cast(needed) + 1 : 1, '\0'); + if (needed > 0) { + std::vsnprintf(buffer.data(), buffer.size(), fmt, args); + } + va_end(args); + current_sink()->write(logger.name(), level, + std::string_view(buffer.data(), needed > 0 ? static_cast(needed) : 0), location); +} + +} // namespace detail + +} // namespace ros2_medkit_gateway::log diff --git a/src/ros2_medkit_gateway/src/default_script_provider.cpp b/src/ros2_medkit_gateway/src/default_script_provider.cpp index 3c9217d6c..ff29d3b50 100644 --- a/src/ros2_medkit_gateway/src/default_script_provider.cpp +++ b/src/ros2_medkit_gateway/src/default_script_provider.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/default_script_provider.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -32,8 +33,6 @@ #include #include -#include - namespace ros2_medkit_gateway { // --- ExecutionState destructor --- @@ -60,9 +59,9 @@ DefaultScriptProvider::DefaultScriptProvider(const ScriptsConfig & config) : con if (!entry.path.empty()) { std::error_code ec; if (!std::filesystem::exists(entry.path, ec) || !std::filesystem::is_regular_file(entry.path, ec)) { - RCLCPP_WARN(rclcpp::get_logger("default_script_provider"), - "Manifest script '%s' path does not exist or is not a regular file: %s", id.c_str(), - entry.path.c_str()); + MEDKIT_LOG_WARN(log::Logger("default_script_provider"), + "Manifest script '%s' path does not exist or is not a regular file: %s", id.c_str(), + entry.path.c_str()); } } } diff --git a/src/ros2_medkit_gateway/src/gateway_node.cpp b/src/ros2_medkit_gateway/src/gateway_node.cpp index 5293ea1f9..c4f4e4036 100644 --- a/src/ros2_medkit_gateway/src/gateway_node.cpp +++ b/src/ros2_medkit_gateway/src/gateway_node.cpp @@ -39,6 +39,7 @@ #include "ros2_medkit_gateway/core/thread_pool_config.hpp" #include "ros2_medkit_gateway/param_utils.hpp" #include "ros2_medkit_gateway/plugins/ros_plugin_context.hpp" +#include "ros2_medkit_gateway/ros2/ros2_log_sink.hpp" #include "ros2_medkit_gateway/core/http/handlers/sse_transport_provider.hpp" #include "ros2_medkit_gateway/core/sqlite_trigger_store.hpp" @@ -48,6 +49,7 @@ using namespace std::chrono_literals; namespace ros2_medkit_gateway { GatewayNode::GatewayNode(const rclcpp::NodeOptions & options) : Node("ros2_medkit_gateway", options) { + log::set_sink(std::make_shared()); RCLCPP_INFO(get_logger(), "Initializing ROS 2 Medkit Gateway..."); // Declare parameters with defaults @@ -1243,7 +1245,7 @@ GatewayNode::GatewayNode(const rclcpp::NodeOptions & options) : Node("ros2_medki "Set aggregation.require_tls=true for production deployments."); } - auto logger = get_logger(); + const log::Logger logger(get_logger().get_name()); aggregation_mgr_ = std::make_unique(agg_config, &logger); // mDNS discovery/announcement @@ -2320,7 +2322,7 @@ void GatewayNode::refresh_cache() { std::unordered_map peer_routing_table; if (aggregation_mgr_ && aggregation_mgr_->peer_count() > 0) { aggregation_mgr_->check_all_health(); - auto logger = get_logger(); + const log::Logger logger(get_logger().get_name()); auto merged = aggregation_mgr_->fetch_and_merge_peer_entities(areas, all_components, apps, functions, 10000, &logger); areas = std::move(merged.areas); diff --git a/src/ros2_medkit_gateway/src/http/fan_out_helpers.cpp b/src/ros2_medkit_gateway/src/http/fan_out_helpers.cpp index 3be98913e..629c46cb3 100644 --- a/src/ros2_medkit_gateway/src/http/fan_out_helpers.cpp +++ b/src/ros2_medkit_gateway/src/http/fan_out_helpers.cpp @@ -13,14 +13,13 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/fan_out_helpers.hpp" - -#include +#include "ros2_medkit_gateway/core/logging.hpp" namespace ros2_medkit_gateway { void log_peer_drop_warning(const char * dto_name, const char * reason) { - RCLCPP_WARN(rclcpp::get_logger("aggregation"), "fan_out_collection: peer item failed to parse as %s, dropping: %s", - dto_name, reason); + MEDKIT_LOG_WARN(log::Logger("aggregation"), "fan_out_collection: peer item failed to parse as %s, dropping: %s", + dto_name, reason); } } // namespace ros2_medkit_gateway diff --git a/src/ros2_medkit_gateway/src/http/handlers/auth_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/auth_handlers.cpp index 188583cad..6aac03975 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/auth_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/auth_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/auth_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -125,7 +126,7 @@ http::Result AuthHandlers::post_authorize(const http::Ty } return to_auth_token_response(*result); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in post_authorize: %s", e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in post_authorize: %s", e.what()); return tl::make_unexpected( make_oauth2_error(500, "server_error", std::string("Internal server error: ") + e.what())); } @@ -165,7 +166,7 @@ http::Result AuthHandlers::post_token(const http::TypedR } return to_auth_token_response(*result); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in post_token: %s", e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in post_token: %s", e.what()); return tl::make_unexpected( make_oauth2_error(500, "server_error", std::string("Internal server error: ") + e.what())); } @@ -213,7 +214,7 @@ http::Result AuthHandlers::post_revoke(const http::Type resp.status = "revoked"; return resp; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in post_revoke: %s", e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in post_revoke: %s", e.what()); return tl::make_unexpected( make_oauth2_error(500, "server_error", std::string("Internal server error: ") + e.what())); } diff --git a/src/ros2_medkit_gateway/src/http/handlers/cyclic_subscription_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/cyclic_subscription_handlers.cpp index ed41690bc..2f0aba9f5 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/cyclic_subscription_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/cyclic_subscription_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/http/handlers/cyclic_subscription_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -21,7 +22,6 @@ #include #include -#include #include "ros2_medkit_gateway/core/http/error_codes.hpp" #include "ros2_medkit_gateway/core/http/http_utils.hpp" @@ -441,7 +441,7 @@ std::string CyclicSubscriptionHandlers::extract_entity_type(const std::string & case SovdEntityType::AREA: case SovdEntityType::UNKNOWN: default: - RCLCPP_WARN(HandlerContext::logger(), "Unexpected entity type in cyclic subscription path: %s", path.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "Unexpected entity type in cyclic subscription path: %s", path.c_str()); return "apps"; } } diff --git a/src/ros2_medkit_gateway/src/http/handlers/data_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/data_handlers.cpp index edf0c4363..975a6f310 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/data_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/data_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/data_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -286,8 +287,8 @@ dto::XMedkitDataItem build_list_item_xmedkit(const std::string & topic_name, con type_info_obj["default_value"] = type_info.default_value; item_xm.type_info = type_info_obj; } catch (const std::exception & e) { - RCLCPP_DEBUG(HandlerContext::logger(), "Could not get type info for topic '%s': %s", topic_name.c_str(), - e.what()); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), "Could not get type info for topic '%s': %s", topic_name.c_str(), + e.what()); } } item_xm.ros2 = ros2_meta; @@ -338,8 +339,8 @@ dto::DataValue build_read_response(const std::string & item_id, const std::strin type_info_obj["default_value"] = type_info.default_value; xm.type_info = type_info_obj; } catch (const std::exception & e) { - RCLCPP_DEBUG(HandlerContext::logger(), "Could not get type info for topic '%s': %s", full_topic_path.c_str(), - e.what()); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), "Could not get type info for topic '%s': %s", full_topic_path.c_str(), + e.what()); } } xm.ros2 = ros2_meta; @@ -446,12 +447,12 @@ http::Result DataHandlers::list_data(const http::TypedReque // contract for exactly this case. return std::move(*result); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin DataProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin DataProvider threw for entity '%s': %s", entity_id.c_str(), + e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin DataProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin DataProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -615,7 +616,7 @@ http::Result DataHandlers::list_data(const http::TypedReque // opaque DataListResult (consistent with the fault list route). return dto::DataListResult{dto::JsonWriter>::write(response)}; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in list_data for entity '%s': %s", entity_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in list_data for entity '%s': %s", entity_id.c_str(), e.what()); return tl::make_unexpected(make_error(500, ERR_INTERNAL_ERROR, "Failed to retrieve entity data", json{{"details", e.what()}, {"entity_id", entity_id}})); } @@ -663,12 +664,12 @@ http::Result DataHandlers::get_data_item(const http::TypedReques } return std::move(*result); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin DataProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin DataProvider threw for entity '%s': %s", entity_id.c_str(), + e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin DataProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin DataProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -714,8 +715,8 @@ http::Result DataHandlers::get_data_item(const http::TypedReques auto type_introspection = data_access_mgr->get_type_introspection(); return build_read_response(addressed->item_id, full_topic_path, *r, entity_id, type_introspection); } catch (const TopicNotAvailableException & e) { - RCLCPP_DEBUG(HandlerContext::logger(), "Topic not available for entity '%s', topic '%s': %s", entity_id.c_str(), - topic_name.c_str(), e.what()); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), "Topic not available for entity '%s', topic '%s': %s", entity_id.c_str(), + topic_name.c_str(), e.what()); return tl::make_unexpected(make_error(404, ERR_X_MEDKIT_ROS2_TOPIC_UNAVAILABLE, "Topic not found", json{{"entity_id", entity_id}, {"topic_name", topic_name}})); } catch (const ProviderErrorException & e) { @@ -728,12 +729,12 @@ http::Result DataHandlers::get_data_item(const http::TypedReques params["topic_name"] = topic_name; const std::string code = info.code.empty() ? std::string{ERR_INTERNAL_ERROR} : info.code; const int status = (info.http_status >= 400 && info.http_status < 600) ? info.http_status : 500; - RCLCPP_WARN(HandlerContext::logger(), "Provider error for entity '%s', topic '%s': %s [%d]", entity_id.c_str(), - topic_name.c_str(), info.message.c_str(), info.http_status); + MEDKIT_LOG_WARN(HandlerContext::logger(), "Provider error for entity '%s', topic '%s': %s [%d]", entity_id.c_str(), + topic_name.c_str(), info.message.c_str(), info.http_status); return tl::make_unexpected(make_error(status, code, info.message, std::move(params))); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in get_data_item for entity '%s', topic '%s': %s", entity_id.c_str(), - topic_name.c_str(), e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in get_data_item for entity '%s', topic '%s': %s", + entity_id.c_str(), topic_name.c_str(), e.what()); return tl::make_unexpected( make_error(500, ERR_INTERNAL_ERROR, "Failed to retrieve topic data", json{{"details", e.what()}, {"entity_id", entity_id}, {"topic_name", topic_name}})); @@ -803,12 +804,12 @@ http::Result DataHandlers::put_data_item(const http::TypedReques data_value.content = std::move(result->content); return data_value; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin DataProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin DataProvider threw for entity '%s': %s", entity_id.c_str(), + e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin DataProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin DataProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -874,8 +875,8 @@ http::Result DataHandlers::put_data_item(const http::TypedReques json publish_result = data_access_mgr->publish_to_topic(full_topic_path, msg_type, data); return build_write_response(addressed->item_id, full_topic_path, msg_type, entity_id, data, publish_result); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in put_data_item for entity '%s', topic '%s': %s", entity_id.c_str(), - topic_name.c_str(), e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in put_data_item for entity '%s', topic '%s': %s", + entity_id.c_str(), topic_name.c_str(), e.what()); return tl::make_unexpected( make_error(500, ERR_INTERNAL_ERROR, "Failed to publish to topic", json{{"details", e.what()}, {"entity_id", entity_id}, {"topic_name", topic_name}})); diff --git a/src/ros2_medkit_gateway/src/http/handlers/discovery_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/discovery_handlers.cpp index b2989e32e..9ab967cb5 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/discovery_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/discovery_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/discovery_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -165,7 +166,7 @@ ErrorInfo make_invalid_id_error(const char * entity_label, const std::string & i } ErrorInfo make_internal_error(const char * where, const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); ErrorInfo err; err.code = ERR_INTERNAL_ERROR; err.message = "Internal server error"; @@ -175,7 +176,7 @@ ErrorInfo make_internal_error(const char * where, const std::exception & e) { } ErrorInfo make_internal_error_no_details(const char * where, const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); ErrorInfo err; err.code = ERR_INTERNAL_ERROR; err.message = "Internal server error"; @@ -1001,8 +1002,8 @@ DiscoveryHandlers::get_component_depends_on(const http::TypedRequest & req) { dto::XMedkitComponent x_medkit_comp; x_medkit_comp.missing = true; item.x_medkit = x_medkit_comp; - RCLCPP_WARN(HandlerContext::logger(), "Component '%s' declares dependency on unknown component '%s'", - component_id.c_str(), dep_id.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "Component '%s' declares dependency on unknown component '%s'", + component_id.c_str(), dep_id.c_str()); } response.items.push_back(std::move(item)); @@ -1303,8 +1304,8 @@ http::Result> DiscoveryHandlers::get_app_depen dto::XMedkitApp x_medkit_app; x_medkit_app.missing = true; item.x_medkit = x_medkit_app; - RCLCPP_WARN(HandlerContext::logger(), "App '%s' declares dependency on unknown app '%s'", app_id.c_str(), - dep_id.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "App '%s' declares dependency on unknown app '%s'", app_id.c_str(), + dep_id.c_str()); } response.items.push_back(std::move(item)); @@ -1379,8 +1380,8 @@ http::Result> DiscoveryHandlers::get_app_belo x_medkit_area.unresolved_component = app.component_id; item.x_medkit = x_medkit_area; response.items.push_back(std::move(item)); - RCLCPP_WARN(HandlerContext::logger(), "App '%s' belongs-to unresolvable: parent component '%s' is unknown", - app_id.c_str(), app.component_id.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "App '%s' belongs-to unresolvable: parent component '%s' is unknown", + app_id.c_str(), app.component_id.c_str()); } else if (!snapshot.component->area.empty()) { const auto & area_id_ref = snapshot.component->area; dto::AreaListItem item; @@ -1395,8 +1396,8 @@ http::Result> DiscoveryHandlers::get_app_belo dto::XMedkitArea x_medkit_area; x_medkit_area.missing = true; item.x_medkit = x_medkit_area; - RCLCPP_WARN(HandlerContext::logger(), "App '%s' belongs to unknown area '%s' (via component '%s')", - app_id.c_str(), area_id_ref.c_str(), app.component_id.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "App '%s' belongs to unknown area '%s' (via component '%s')", + app_id.c_str(), area_id_ref.c_str(), app.component_id.c_str()); } response.items.push_back(std::move(item)); } @@ -1475,8 +1476,8 @@ DiscoveryHandlers::get_app_is_located_on(const http::TypedRequest & req) { item.x_medkit = x_medkit_comp; response.items.push_back(std::move(item)); - RCLCPP_WARN(HandlerContext::logger(), "App '%s' references unknown component '%s'", app_id.c_str(), - app.component_id.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "App '%s' references unknown component '%s'", app_id.c_str(), + app.component_id.c_str()); } } diff --git a/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp index 0df7419b9..a49ce4cfa 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/http/handlers/fault_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -383,10 +384,10 @@ dto::FaultDetail FaultHandlers::build_sovd_fault_response(const json & fault_jso // row instead of advertising it. if (!s.contains("bulk_data_id") || !s["bulk_data_id"].is_string() || s["bulk_data_id"].get().empty()) { - RCLCPP_WARN(HandlerContext::logger(), - "Rosbag snapshot for fault '%s' on entity '%s' carries no 'bulk_data_id'; omitting it, as " - "nothing could address the recording. This is a transport-side bug.", - fault_code.c_str(), entity_path.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), + "Rosbag snapshot for fault '%s' on entity '%s' carries no 'bulk_data_id'; omitting it, as " + "nothing could address the recording. This is a transport-side bug.", + fault_code.c_str(), entity_path.c_str()); continue; } std::string bulk_data_uri = entity_path; @@ -508,7 +509,7 @@ http::Result FaultHandlers::list_all_faults(const http::Ty response["x-medkit"] = dto::JsonWriter::write(xm); return wrap_list_result(std::move(response)); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in list_all_faults: %s", e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in list_all_faults: %s", e.what()); return tl::make_unexpected( make_error(500, ERR_INTERNAL_ERROR, "Failed to list faults", json{{"details", e.what()}})); } @@ -548,12 +549,12 @@ http::Result FaultHandlers::list_faults(const http::TypedR } return std::move(*result); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -717,7 +718,7 @@ http::Result FaultHandlers::list_faults(const http::TypedR response["x-medkit"] = std::move(xm_json); return wrap_list_result(std::move(response)); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in list_faults for entity '%s': %s", entity_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in list_faults for entity '%s': %s", entity_id.c_str(), e.what()); return tl::make_unexpected(make_error(500, ERR_INTERNAL_ERROR, "Failed to list faults", json{{"details", e.what()}, {"entity_id", entity_id}})); } @@ -800,12 +801,12 @@ http::Result FaultHandlers::get_fault(const http::TypedR } return std::move(*result); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -844,8 +845,8 @@ http::Result FaultHandlers::get_fault(const http::TypedR return wrap_detail_result(dto::JsonWriter::write(detail)); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in get_fault for entity '%s', fault '%s': %s", entity_id.c_str(), - fault_code.c_str(), e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in get_fault for entity '%s', fault '%s': %s", entity_id.c_str(), + fault_code.c_str(), e.what()); return tl::make_unexpected( make_error(500, ERR_INTERNAL_ERROR, "Failed to get fault", json{{"details", e.what()}, {"entity_id", entity_id}, {"fault_code", fault_code}})); @@ -937,12 +938,12 @@ FaultHandlers::clear_fault(const http::TypedRequest & req) { } return Outcome{std::move(*result)}; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -980,8 +981,8 @@ FaultHandlers::clear_fault(const http::TypedRequest & req) { } return Outcome{http::NoContent{}}; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in clear_fault for entity '%s', fault '%s': %s", entity_id.c_str(), - fault_code.c_str(), e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in clear_fault for entity '%s', fault '%s': %s", + entity_id.c_str(), fault_code.c_str(), e.what()); return tl::make_unexpected( make_error(500, ERR_INTERNAL_ERROR, "Failed to clear fault", json{{"details", e.what()}, {"entity_id", entity_id}, {"fault_code", fault_code}})); @@ -1044,12 +1045,12 @@ http::Result FaultHandlers::clear_all_faults(const http::TypedR } return http::NoContent{}; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin FaultProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -1083,15 +1084,15 @@ http::Result FaultHandlers::clear_all_faults(const http::TypedR std::string code = fault["fault_code"].get(); auto clear_result = fault_mgr->clear_fault(code, /*skip_correlation_auto_clear=*/true); if (!clear_result.success) { - RCLCPP_WARN(HandlerContext::logger(), "Failed to clear fault '%s' for entity '%s': %s", code.c_str(), - entity_id.c_str(), clear_result.error_message.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "Failed to clear fault '%s' for entity '%s': %s", code.c_str(), + entity_id.c_str(), clear_result.error_message.c_str()); } } } return http::NoContent{}; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in clear_all_faults for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in clear_all_faults for entity '%s': %s", entity_id.c_str(), + e.what()); return tl::make_unexpected(make_error(500, ERR_INTERNAL_ERROR, "Failed to clear faults", json{{"details", e.what()}, {"entity_id", entity_id}})); } @@ -1167,8 +1168,8 @@ FaultHandlers::clear_all_faults_global(const http::TypedRequest & req) { std::string code = fault["fault_code"].get(); auto clear_result = fault_mgr->clear_fault(code); if (!clear_result.success) { - RCLCPP_WARN(HandlerContext::logger(), "Failed to clear fault '%s': %s", code.c_str(), - clear_result.error_message.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "Failed to clear fault '%s': %s", code.c_str(), + clear_result.error_message.c_str()); } } } @@ -1186,7 +1187,7 @@ FaultHandlers::clear_all_faults_global(const http::TypedRequest & req) { att.with_header("X-Medkit-Local-Only", "true"); return std::pair{http::NoContent{}, std::move(att)}; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in clear_all_faults_global: %s", e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in clear_all_faults_global: %s", e.what()); return tl::make_unexpected( make_error(500, ERR_INTERNAL_ERROR, "Failed to clear faults", json{{"details", e.what()}})); } diff --git a/src/ros2_medkit_gateway/src/http/handlers/health_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/health_handlers.cpp index cbe232f20..54be5ee14 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/health_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/health_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/health_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include @@ -40,7 +41,7 @@ namespace handlers { namespace { ErrorInfo make_internal_error(const char * where, const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); ErrorInfo err; err.code = ERR_INTERNAL_ERROR; err.message = "Internal server error"; @@ -190,10 +191,10 @@ http::Result HealthHandlers::get_health(const http::TypedRequest & if (auto valid = validate_entity_id(w.entity_id); valid) { warning.entity_ids = {w.entity_id}; } else { - RCLCPP_WARN(HandlerContext::logger(), - "Peer(s) [%s] announced Component id '%s', which is not a valid entity id (%s); omitting it " - "from the '%s' warning's entity_ids", - peers_list.c_str(), w.entity_id.c_str(), valid.error().c_str(), WARN_LEAF_ID_COLLISION); + MEDKIT_LOG_WARN(HandlerContext::logger(), + "Peer(s) [%s] announced Component id '%s', which is not a valid entity id (%s); omitting it " + "from the '%s' warning's entity_ids", + peers_list.c_str(), w.entity_id.c_str(), valid.error().c_str(), WARN_LEAF_ID_COLLISION); } warning.peer_names = w.peer_names; warnings.push_back(std::move(warning)); diff --git a/src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp index ddc0fae55..bc906a6ef 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/lifecycle_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -97,8 +98,8 @@ http::Result LifecycleHandlers::handle_get_status( // The JSON writer only validates the status enum on read, not on write, // so guard the SOVD ready/notReady contract against a misbehaving plugin. if (resp.status != "ready" && resp.status != "notReady") { - RCLCPP_ERROR(HandlerContext::logger(), "LifecycleProvider returned invalid status '%s' for entity '%s'", - resp.status.c_str(), entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "LifecycleProvider returned invalid status '%s' for entity '%s'", + resp.status.c_str(), entity_id.c_str()); return tl::make_unexpected(make_error(500, ERR_PLUGIN_ERROR, "Plugin returned invalid lifecycle status")); } // Fill absolute transition URIs for each present field. @@ -119,12 +120,12 @@ http::Result LifecycleHandlers::handle_get_status( } return resp; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_error(500, ERR_PLUGIN_ERROR, "Plugin threw exception")); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected(make_error(500, ERR_PLUGIN_ERROR, "Plugin threw unknown exception")); } } @@ -222,12 +223,12 @@ LifecycleHandlers::handle_transition(const http::TypedRequest & req, std::string att.with_location(base + "/status"); return std::make_pair(http::Accepted{http::NoContent{}}, std::move(att)); } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_error(500, ERR_PLUGIN_ERROR, "Plugin threw exception")); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin LifecycleProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected(make_error(500, ERR_PLUGIN_ERROR, "Plugin threw unknown exception")); } } diff --git a/src/ros2_medkit_gateway/src/http/handlers/lock_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/lock_handlers.cpp index f276ef45a..fc62994d9 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/lock_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/lock_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/lock_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -38,7 +39,7 @@ namespace { /// for unrelated entity ids. ErrorInfo make_internal_error(const char * where, const std::string & message, const std::exception & e, json params = {}) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); if (params.is_null()) { params = json::object(); } diff --git a/src/ros2_medkit_gateway/src/http/handlers/operation_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/operation_handlers.cpp index 50751a897..141c23eff 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/operation_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/operation_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/operation_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -379,8 +380,8 @@ dto::XMedkitOperationItem build_service_xmedkit(const ServiceInfo & svc, const s // of rebuilding + deep-copying (issue #442). x_medkit.type_info = *type_introspection->get_service_type_info(svc.type); } catch (const std::exception & e) { - RCLCPP_DEBUG(HandlerContext::logger(), "Could not get type info for service '%s': %s", svc.type.c_str(), - e.what()); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), "Could not get type info for service '%s': %s", svc.type.c_str(), + e.what()); } } return x_medkit; @@ -402,7 +403,8 @@ dto::XMedkitOperationItem build_action_xmedkit(const ActionInfo & act, const std try { x_medkit.type_info = *type_introspection->get_action_type_info(act.type); } catch (const std::exception & e) { - RCLCPP_DEBUG(HandlerContext::logger(), "Could not get type info for action '%s': %s", act.type.c_str(), e.what()); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), "Could not get type info for action '%s': %s", act.type.c_str(), + e.what()); } } return x_medkit; @@ -631,12 +633,12 @@ http::Result> OperationHandlers::list_operat } return *result; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -648,8 +650,8 @@ http::Result> OperationHandlers::list_operat return tl::make_unexpected(lookup.error()); } const auto & ops = lookup->ops; - RCLCPP_DEBUG(HandlerContext::logger(), "Listing operations for %s '%s': %zu services, %zu actions", - lookup->entity_type.c_str(), entity_id.c_str(), ops.services.size(), ops.actions.size()); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), "Listing operations for %s '%s': %zu services, %zu actions", + lookup->entity_type.c_str(), entity_id.c_str(), ops.services.size(), ops.actions.size()); // Build OperationItem list (services + actions). The wire shape is shared // with handle_get_operation so build_service_xmedkit / build_action_xmedkit @@ -892,12 +894,12 @@ http::Result OperationHandlers::get_operation(const http:: detail.item = std::move(*result); return detail; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -1036,12 +1038,12 @@ OperationHandlers::create_execution(const http::TypedRequest & req, dto::Executi } return SuccessPair{ResultVariant{std::move(*result)}, http::ResponseAttachments{}}; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } @@ -1233,12 +1235,12 @@ http::Result> OperationHandlers::list_executio } return collection; } catch (const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", entity_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw for entity '%s': %s", + entity_id.c_str(), e.what()); return tl::make_unexpected(make_plugin_error(500, "Plugin threw exception", json{{"entity_id", entity_id}})); } catch (...) { - RCLCPP_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", - entity_id.c_str()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Plugin OperationProvider threw unknown exception for entity '%s'", + entity_id.c_str()); return tl::make_unexpected( make_plugin_error(500, "Plugin threw unknown exception", json{{"entity_id", entity_id}})); } diff --git a/src/ros2_medkit_gateway/src/http/handlers/sse_fault_handler.cpp b/src/ros2_medkit_gateway/src/http/handlers/sse_fault_handler.cpp index 00d4816d4..58aa8906f 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/sse_fault_handler.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/sse_fault_handler.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/http/handlers/sse_fault_handler.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -61,9 +62,9 @@ SSEFaultHandler::SSEFaultHandler(HandlerContext & ctx, std::shared_ptrmax_clients()); + MEDKIT_LOG_INFO(HandlerContext::logger(), "SSE fault handler initialized, subscribed to %s, max_clients=%zu", + fault_events_topic.c_str(), client_tracker_->max_clients()); } SSEFaultHandler::~SSEFaultHandler() { @@ -252,8 +253,8 @@ void SSEFaultHandler::on_fault_event(const ros2_medkit_msgs::msg::FaultEvent::Co void SSEFaultHandler::on_peer_event(const StreamEvent & event) { auto payload = nlohmann::json::parse(event.data, nullptr, false); if (payload.is_discarded() || !payload.is_object()) { - RCLCPP_WARN(HandlerContext::logger(), "Discarding unparseable fault event relayed from peer '%s'", - event.peer_name.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "Discarding unparseable fault event relayed from peer '%s'", + event.peer_name.c_str()); return; } @@ -333,18 +334,18 @@ void SSEFaultHandler::enqueue(QueuedEvent queued) { const auto previous = dropped_events_.fetch_add(stats.dropped); const auto total = previous + stats.dropped; if (previous == 0 || (total / kDropLogEveryN) > (previous / kDropLogEveryN)) { - RCLCPP_WARN(HandlerContext::logger(), - "SSE fault event lost: %zu event(s) dropped total for %zu slow client(s) " - "(buffer cap=%zu, %zu coalesced so far)", - total, stats.slow_clients, kMaxBufferedEvents, coalesced_events_.load()); + MEDKIT_LOG_WARN(HandlerContext::logger(), + "SSE fault event lost: %zu event(s) dropped total for %zu slow client(s) " + "(buffer cap=%zu, %zu coalesced so far)", + total, stats.slow_clients, kMaxBufferedEvents, coalesced_events_.load()); } } // Notify all waiting clients queue_cv_.notify_all(); - RCLCPP_DEBUG(HandlerContext::logger(), "Received fault event: %s for %s (id=%" PRIu64 ")", event_type.c_str(), - fault_code.c_str(), event_id); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), "Received fault event: %s for %s (id=%" PRIu64 ")", event_type.c_str(), + fault_code.c_str(), event_id); } namespace { @@ -533,8 +534,8 @@ http::Result SSEFaultHandler::sse_stream(const http::TypedReque const auto & raw_req = req.raw_for_framework(); #pragma GCC diagnostic pop if (!client_tracker_->try_connect()) { - RCLCPP_WARN(HandlerContext::logger(), "SSE client limit reached (%zu), rejecting connection from %s", - client_tracker_->max_clients(), raw_req.remote_addr.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "SSE client limit reached (%zu), rejecting connection from %s", + client_tracker_->max_clients(), raw_req.remote_addr.c_str()); ErrorInfo err; err.code = ERR_SERVICE_UNAVAILABLE; err.message = "Maximum number of SSE clients reached. Please try again later."; @@ -542,8 +543,8 @@ http::Result SSEFaultHandler::sse_stream(const http::TypedReque return tl::make_unexpected(std::move(err)); } - RCLCPP_INFO(HandlerContext::logger(), "SSE fault client connected from %s (%zu/%zu)", raw_req.remote_addr.c_str(), - client_tracker_->connected_clients(), client_tracker_->max_clients()); + MEDKIT_LOG_INFO(HandlerContext::logger(), "SSE fault client connected from %s (%zu/%zu)", raw_req.remote_addr.c_str(), + client_tracker_->connected_clients(), client_tracker_->max_clients()); const uint64_t last_event_id = parse_last_event_id(req.header("Last-Event-ID").value_or(std::string{})); @@ -554,7 +555,7 @@ http::Result SSEFaultHandler::sse_stream(const http::TypedReque // analogous to the legacy `handle_stream`'s 3-arg overload. auto release_guard = std::shared_ptr(nullptr, [this, addr = raw_req.remote_addr](void *) { client_tracker_->disconnect(); - RCLCPP_INFO(HandlerContext::logger(), "SSE fault client disconnected from %s", addr.c_str()); + MEDKIT_LOG_INFO(HandlerContext::logger(), "SSE fault client disconnected from %s", addr.c_str()); }); // An aggregating gateway relaying from another sends X-Medkit-No-Fan-Out, @@ -573,8 +574,8 @@ http::Result SSEFaultHandler::sse_stream(const http::TypedReque void SSEFaultHandler::handle_stream(const httplib::Request & req, httplib::Response & res) { // Check if we're at the combined SSE client limit before accepting connection if (!client_tracker_->try_connect()) { - RCLCPP_WARN(HandlerContext::logger(), "SSE client limit reached (%zu), rejecting connection from %s", - client_tracker_->max_clients(), req.remote_addr.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "SSE client limit reached (%zu), rejecting connection from %s", + client_tracker_->max_clients(), req.remote_addr.c_str()); // The legacy raw-route entry calls the framework primitive directly via // the friend gate; the HandlerContext public send_* surface has been // pruned and the typed `sse_stream` path is the production route. @@ -586,8 +587,8 @@ void SSEFaultHandler::handle_stream(const httplib::Request & req, httplib::Respo return; } - RCLCPP_INFO(HandlerContext::logger(), "SSE fault client connected from %s (%zu/%zu)", req.remote_addr.c_str(), - client_tracker_->connected_clients(), client_tracker_->max_clients()); + MEDKIT_LOG_INFO(HandlerContext::logger(), "SSE fault client connected from %s (%zu/%zu)", req.remote_addr.c_str(), + client_tracker_->connected_clients(), client_tracker_->max_clients()); const uint64_t last_event_id = req.has_header("Last-Event-ID") ? parse_last_event_id(req.get_header_value("Last-Event-ID")) : 0; @@ -611,8 +612,8 @@ void SSEFaultHandler::handle_stream(const httplib::Request & req, httplib::Respo }, [this, addr = req.remote_addr](bool success) { client_tracker_->disconnect(); - RCLCPP_INFO(HandlerContext::logger(), "SSE fault client disconnected from %s (success=%d)", addr.c_str(), - success); + MEDKIT_LOG_INFO(HandlerContext::logger(), "SSE fault client disconnected from %s (success=%d)", addr.c_str(), + success); }); } @@ -738,10 +739,10 @@ SSEFaultHandler::resolve_entity_context(const ros2_medkit_msgs::msg::Fault & fau } if (entity_id.empty()) { - RCLCPP_DEBUG(HandlerContext::logger(), - "SSE fault event: no entity match for reporting source '%s' (fault_code='%s'); " - "omitting x-medkit extension", - raw_fqn.c_str(), fault.fault_code.c_str()); + MEDKIT_LOG_DEBUG(HandlerContext::logger(), + "SSE fault event: no entity match for reporting source '%s' (fault_code='%s'); " + "omitting x-medkit extension", + raw_fqn.c_str(), fault.fault_code.c_str()); return std::nullopt; } diff --git a/src/ros2_medkit_gateway/src/http/handlers/sse_transport_provider.cpp b/src/ros2_medkit_gateway/src/http/handlers/sse_transport_provider.cpp index 0fbcd94f8..d3fa0dfaf 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/sse_transport_provider.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/sse_transport_provider.cpp @@ -13,14 +13,13 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/sse_transport_provider.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include #include #include -#include - #include "ros2_medkit_gateway/core/http/error_codes.hpp" #include "ros2_medkit_gateway/core/http/http_utils.hpp" #include "ros2_medkit_gateway/http/handlers/handler_context.hpp" @@ -82,8 +81,9 @@ tl::expected SseTransportProvider::make_sse_stream(c // the legacy handle_client_connect path, exhaustion produces a 503 SOVD // GenericError that the typed wrapper renders for the framework. if (!client_tracker_->try_connect()) { - RCLCPP_WARN(handlers::HandlerContext::logger(), - "SSE client limit reached (%zu), rejecting cyclic subscription stream", client_tracker_->max_clients()); + MEDKIT_LOG_WARN(handlers::HandlerContext::logger(), + "SSE client limit reached (%zu), rejecting cyclic subscription stream", + client_tracker_->max_clients()); ErrorInfo err; err.code = ERR_SERVICE_UNAVAILABLE; err.message = "Maximum number of SSE clients reached. Please try again later."; @@ -91,8 +91,8 @@ tl::expected SseTransportProvider::make_sse_stream(c return tl::make_unexpected(std::move(err)); } - RCLCPP_INFO(handlers::HandlerContext::logger(), "SSE cyclic subscription client connected (%zu/%zu)", - client_tracker_->connected_clients(), client_tracker_->max_clients()); + MEDKIT_LOG_INFO(handlers::HandlerContext::logger(), "SSE cyclic subscription client connected (%zu/%zu)", + client_tracker_->connected_clients(), client_tracker_->max_clients()); // The tracker_guard releases the SSE client slot on closure destruction. The // framework holds the SseStream via a shared_ptr, so the guard's deleter @@ -106,8 +106,8 @@ tl::expected SseTransportProvider::make_sse_stream(c std::string captured_sub_id = sub_id; // NOLINT(performance-unnecessary-copy-initialization) std::shared_ptr tracker_guard(nullptr, [tracker, captured_sub_id](void *) { tracker->disconnect(); - RCLCPP_DEBUG(rclcpp::get_logger("rest_server"), "SSE cyclic subscription stream disconnected: %s", - captured_sub_id.c_str()); + MEDKIT_LOG_DEBUG(log::Logger("rest_server"), "SSE cyclic subscription stream disconnected: %s", + captured_sub_id.c_str()); }); http::SseStream stream; @@ -171,8 +171,8 @@ tl::expected SseTransportProvider::make_sse_stream(c envelope["error"] = error; } } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("sse_transport"), "Sampler exception for sub %s: %s", captured_sub_id.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("sse_transport"), "Sampler exception for sub %s: %s", captured_sub_id.c_str(), + e.what()); json error; error["error_code"] = ERR_INTERNAL_ERROR; error["message"] = "Internal error during resource sampling"; diff --git a/src/ros2_medkit_gateway/src/http/handlers/trigger_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/trigger_handlers.cpp index 59ab2a3db..4b913d4c1 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/trigger_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/trigger_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/trigger_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -25,8 +26,6 @@ #include #include -#include - #include "ros2_medkit_gateway/core/data_point_suggest.hpp" #include "ros2_medkit_gateway/core/http/error_codes.hpp" #include "ros2_medkit_gateway/core/http/http_utils.hpp" @@ -287,10 +286,10 @@ TriggerHandlers::post_trigger(const http::TypedRequest & req, dto::TriggerCreate } } if (resolved_topic_name.empty()) { - RCLCPP_WARN(HandlerContext::logger(), - "Could not resolve resource_path '%s' to a ROS 2 topic for entity '%s'. " - "Topic subscription will be attempted when topic becomes available.", - parsed->resource_path.c_str(), entity_id.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), + "Could not resolve resource_path '%s' to a ROS 2 topic for entity '%s'. " + "Topic subscription will be attempted when topic becomes available.", + parsed->resource_path.c_str(), entity_id.c_str()); } } @@ -596,7 +595,7 @@ std::string TriggerHandlers::extract_entity_type(const std::string & path) { case SovdEntityType::SERVER: case SovdEntityType::UNKNOWN: default: - RCLCPP_WARN(HandlerContext::logger(), "Unexpected entity type in trigger path: %s", path.c_str()); + MEDKIT_LOG_WARN(HandlerContext::logger(), "Unexpected entity type in trigger path: %s", path.c_str()); return "apps"; } } diff --git a/src/ros2_medkit_gateway/src/http/handlers/update_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/update_handlers.cpp index 5d391c3e6..782719409 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/update_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/update_handlers.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/handlers/update_handlers.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include @@ -31,7 +32,7 @@ namespace { /// Wrap a thrown std::exception as a 500 internal-error ErrorInfo, logging /// the original `what()` via the shared handler logger. ErrorInfo make_internal_error(const char * where, const std::exception & e) { - RCLCPP_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); + MEDKIT_LOG_ERROR(HandlerContext::logger(), "Error in %s: %s", where, e.what()); return make_error(500, ERR_INTERNAL_ERROR, e.what()); } diff --git a/src/ros2_medkit_gateway/src/http/http_server.cpp b/src/ros2_medkit_gateway/src/http/http_server.cpp index 3979bd477..a2d650c46 100644 --- a/src/ros2_medkit_gateway/src/http/http_server.cpp +++ b/src/ros2_medkit_gateway/src/http/http_server.cpp @@ -13,8 +13,8 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/http_server.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" -#include #include namespace ros2_medkit_gateway { @@ -38,14 +38,14 @@ HttpServerManager::HttpServerManager(const TlsConfig & tls_config, std::size_t t apply_thread_pool(*ssl_server_); apply_keep_alive(*ssl_server_); - RCLCPP_INFO(rclcpp::get_logger("http_server"), "TLS/HTTPS enabled - cert: %s, min_version: %s", - tls_config_.cert_file.c_str(), tls_config_.min_version.c_str()); + MEDKIT_LOG_INFO(log::Logger("http_server"), "TLS/HTTPS enabled - cert: %s, min_version: %s", + tls_config_.cert_file.c_str(), tls_config_.min_version.c_str()); // Note: key_file path intentionally not logged for security reasons } else { server_ = std::make_unique(); apply_thread_pool(*server_); apply_keep_alive(*server_); - RCLCPP_DEBUG(rclcpp::get_logger("http_server"), "TLS/HTTPS disabled - using plain HTTP"); + MEDKIT_LOG_DEBUG(log::Logger("http_server"), "TLS/HTTPS disabled - using plain HTTP"); } #else if (tls_config_.enabled) { @@ -95,8 +95,7 @@ httplib::Server * HttpServerManager::get_server() { void HttpServerManager::listen(const std::string & host, int port) { std::string protocol = tls_config_.enabled ? "HTTPS" : "HTTP"; - RCLCPP_INFO(rclcpp::get_logger("http_server"), "Starting %s server on %s:%d...", protocol.c_str(), host.c_str(), - port); + MEDKIT_LOG_INFO(log::Logger("http_server"), "Starting %s server on %s:%d...", protocol.c_str(), host.c_str(), port); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT if (tls_config_.enabled && ssl_server_) { @@ -112,13 +111,13 @@ void HttpServerManager::listen(const std::string & host, int port) { void HttpServerManager::stop() { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT if (tls_config_.enabled && ssl_server_ && ssl_server_->is_running()) { - RCLCPP_INFO(rclcpp::get_logger("http_server"), "Stopping HTTPS server..."); + MEDKIT_LOG_INFO(log::Logger("http_server"), "Stopping HTTPS server..."); ssl_server_->stop(); return; } #endif if (server_ && server_->is_running()) { - RCLCPP_INFO(rclcpp::get_logger("http_server"), "Stopping HTTP server..."); + MEDKIT_LOG_INFO(log::Logger("http_server"), "Stopping HTTP server..."); server_->stop(); } } @@ -154,17 +153,17 @@ void HttpServerManager::configure_tls() { // to expose SSL_CTX for SSL_CTX_set_verify() with SSL_VERIFY_PEER if (tls_config_.min_version != "1.2") { - RCLCPP_WARN(rclcpp::get_logger("http_server"), - "min_version='%s' requested but cpp-httplib uses OpenSSL defaults (TLS 1.2+). " - "Custom min_version not enforced.", - tls_config_.min_version.c_str()); + MEDKIT_LOG_WARN(log::Logger("http_server"), + "min_version='%s' requested but cpp-httplib uses OpenSSL defaults (TLS 1.2+). " + "Custom min_version not enforced.", + tls_config_.min_version.c_str()); } // Log TLS handshake failures for debugging ssl_server_->set_logger([](const httplib::Request & req, const httplib::Response & res) { if (res.status >= 400) { - RCLCPP_DEBUG(rclcpp::get_logger("http_server"), "Request %s %s -> %d", req.method.c_str(), req.path.c_str(), - res.status); + MEDKIT_LOG_DEBUG(log::Logger("http_server"), "Request %s %s -> %d", req.method.c_str(), req.path.c_str(), + res.status); } }); } diff --git a/src/ros2_medkit_gateway/src/http/rest_server.cpp b/src/ros2_medkit_gateway/src/http/rest_server.cpp index 5b070aece..9d8ebefe7 100644 --- a/src/ros2_medkit_gateway/src/http/rest_server.cpp +++ b/src/ros2_medkit_gateway/src/http/rest_server.cpp @@ -13,13 +13,13 @@ // limitations under the License. #include "ros2_medkit_gateway/core/http/rest_server.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include #include #include #include -#include #include #include @@ -59,20 +59,20 @@ struct HttplibExceptionHandlerAdapter { if (ep) { std::rethrow_exception(ep); } - RCLCPP_ERROR(rclcpp::get_logger("rest_server"), "Unhandled empty exception_ptr"); + MEDKIT_LOG_ERROR(log::Logger("rest_server"), "Unhandled empty exception_ptr"); set_internal_server_error(res, "Unknown exception"); } catch (const std::exception & e) { - RCLCPP_ERROR(rclcpp::get_logger("rest_server"), "Unhandled exception: %s", e.what()); + MEDKIT_LOG_ERROR(log::Logger("rest_server"), "Unhandled exception: %s", e.what()); set_internal_server_error(res, "An internal error occurred"); } catch (...) { - RCLCPP_ERROR(rclcpp::get_logger("rest_server"), "Unknown exception caught"); + MEDKIT_LOG_ERROR(log::Logger("rest_server"), "Unknown exception caught"); set_internal_server_error(res, "An internal error occurred"); } } // Older cpp-httplib versions pass std::exception directly. void operator()(const httplib::Request & /*req*/, httplib::Response & res, const std::exception & e) const { - RCLCPP_ERROR(rclcpp::get_logger("rest_server"), "Unhandled exception: %s", e.what()); + MEDKIT_LOG_ERROR(log::Logger("rest_server"), "Unhandled exception: %s", e.what()); set_internal_server_error(res, "An internal error occurred"); } }; @@ -107,20 +107,20 @@ RESTServer::RESTServer(GatewayNode * node, const std::string & host, int port, c const auto requested_pool_size = node_->get_parameter("server.http_thread_pool_size").as_int(); const auto http_thread_pool_size = clamp_thread_count(requested_pool_size, 1, 1024); if (static_cast(http_thread_pool_size) != requested_pool_size) { - RCLCPP_WARN(rclcpp::get_logger("rest_server"), "server.http_thread_pool_size %" PRId64 " clamped to %zu", - requested_pool_size, http_thread_pool_size); + MEDKIT_LOG_WARN(log::Logger("rest_server"), "server.http_thread_pool_size %" PRId64 " clamped to %zu", + requested_pool_size, http_thread_pool_size); } const auto requested_keep_alive = node_->get_parameter("server.keep_alive_timeout_sec").as_int(); const auto keep_alive_timeout_sec = clamp_keep_alive_timeout(requested_keep_alive, 1, 3600); if (static_cast(keep_alive_timeout_sec) != requested_keep_alive) { - RCLCPP_WARN(rclcpp::get_logger("rest_server"), "server.keep_alive_timeout_sec %" PRId64 " clamped to %" PRId64, - requested_keep_alive, static_cast(keep_alive_timeout_sec)); + MEDKIT_LOG_WARN(log::Logger("rest_server"), "server.keep_alive_timeout_sec %" PRId64 " clamped to %" PRId64, + requested_keep_alive, static_cast(keep_alive_timeout_sec)); } http_server_ = std::make_unique(tls_config_, http_thread_pool_size, keep_alive_timeout_sec); - RCLCPP_INFO(rclcpp::get_logger("rest_server"), - "HTTP request thread pool bounded to %zu workers (each active SSE stream holds one), " - "keep-alive timeout %" PRId64 "s", - http_thread_pool_size, static_cast(keep_alive_timeout_sec)); + MEDKIT_LOG_INFO(log::Logger("rest_server"), + "HTTP request thread pool bounded to %zu workers (each active SSE stream holds one), " + "keep-alive timeout %" PRId64 "s", + http_thread_pool_size, static_cast(keep_alive_timeout_sec)); // Set maximum payload size for uploads (cpp-httplib default is 8MB) auto * srv = http_server_->get_server(); @@ -128,7 +128,7 @@ RESTServer::RESTServer(GatewayNode * node, const std::string & host, int port, c size_t max_payload = node_->get_bulk_data_store() ? node_->get_bulk_data_store()->max_upload_bytes() : 0; if (max_payload > 0) { srv->set_payload_max_length(max_payload); - RCLCPP_INFO(rclcpp::get_logger("rest_server"), "Max payload length set to %zu bytes", max_payload); + MEDKIT_LOG_INFO(log::Logger("rest_server"), "Max payload length set to %zu bytes", max_payload); } } @@ -136,18 +136,18 @@ RESTServer::RESTServer(GatewayNode * node, const std::string & host, int port, c if (auth_config_.enabled) { auth_manager_ = std::make_unique(auth_config_); auth_middleware_ = std::make_unique(auth_config_, auth_manager_.get()); - RCLCPP_INFO(rclcpp::get_logger("rest_server"), "Authentication enabled - algorithm: %s, require_auth_for: %s", - algorithm_to_string(auth_config_.jwt_algorithm).c_str(), - auth_config_.require_auth_for == AuthRequirement::NONE ? "none" - : auth_config_.require_auth_for == AuthRequirement::WRITE ? "write" - : "all"); + MEDKIT_LOG_INFO(log::Logger("rest_server"), "Authentication enabled - algorithm: %s, require_auth_for: %s", + algorithm_to_string(auth_config_.jwt_algorithm).c_str(), + auth_config_.require_auth_for == AuthRequirement::NONE ? "none" + : auth_config_.require_auth_for == AuthRequirement::WRITE ? "write" + : "all"); } // Initialize rate limiter if enabled if (rate_limit_config.enabled) { rate_limiter_ = std::make_unique(rate_limit_config); - RCLCPP_INFO(rclcpp::get_logger("rest_server"), "Rate limiting enabled - global: %d rpm, per-client: %d rpm", - rate_limit_config.global_requests_per_minute, rate_limit_config.client_requests_per_minute); + MEDKIT_LOG_INFO(log::Logger("rest_server"), "Rate limiting enabled - global: %d rpm, per-client: %d rpm", + rate_limit_config.global_requests_per_minute, rate_limit_config.client_requests_per_minute); } // Create handler context and domain-specific handlers @@ -198,8 +198,8 @@ RESTServer::RESTServer(GatewayNode * node, const std::string & host, int port, c const int64_t keepalive_raw = node_->get_parameter("sse.keepalive_interval_sec").as_int(); const int64_t keepalive_used = std::clamp(keepalive_raw, kMinKeepaliveSec, kMaxKeepaliveSec); if (keepalive_used != keepalive_raw) { - RCLCPP_WARN(node_->get_logger(), "sse.keepalive_interval_sec %" PRId64 " clamped to %" PRId64, keepalive_raw, - keepalive_used); + MEDKIT_LOG_WARN(log::Logger(node_->get_logger().get_name()), + "sse.keepalive_interval_sec %" PRId64 " clamped to %" PRId64, keepalive_raw, keepalive_used); } sse_fault_handler_ = std::make_unique(*handler_ctx_, sse_client_tracker_, std::chrono::seconds(keepalive_used)); @@ -211,8 +211,9 @@ RESTServer::RESTServer(GatewayNode * node, const std::string & host, int port, c const int64_t max_duration_raw = node_->get_parameter("sse.max_duration_sec").as_int(); int max_duration_sec = 3600; if (max_duration_raw < 1 || max_duration_raw > std::numeric_limits::max()) { - RCLCPP_WARN(rclcpp::get_logger("rest_server"), - "sse.max_duration_sec %" PRId64 " must be in [1, 2147483647]. Using default 3600.", max_duration_raw); + MEDKIT_LOG_WARN(log::Logger("rest_server"), + "sse.max_duration_sec %" PRId64 " must be in [1, 2147483647]. Using default 3600.", + max_duration_raw); } else { max_duration_sec = static_cast(max_duration_raw); } @@ -2577,20 +2578,20 @@ void RESTServer::report_route_metadata_issues() const { for (const auto & issue : route_registry_->validate_completeness()) { if (issue.severity == openapi::ValidationIssue::Severity::kError) { ++errors; - RCLCPP_ERROR(rclcpp::get_logger("rest_server"), "OpenAPI metadata error on %s: %s", issue.route.c_str(), - issue.message.c_str()); + MEDKIT_LOG_ERROR(log::Logger("rest_server"), "OpenAPI metadata error on %s: %s", issue.route.c_str(), + issue.message.c_str()); } else { ++warnings; - RCLCPP_WARN(rclcpp::get_logger("rest_server"), "OpenAPI metadata warning on %s: %s", issue.route.c_str(), - issue.message.c_str()); + MEDKIT_LOG_WARN(log::Logger("rest_server"), "OpenAPI metadata warning on %s: %s", issue.route.c_str(), + issue.message.c_str()); } } - RCLCPP_INFO(rclcpp::get_logger("rest_server"), - "OpenAPI metadata check: %zu error(s), %zu warning(s) across %zu route(s)", errors, warnings, - route_registry_->size()); + MEDKIT_LOG_INFO(log::Logger("rest_server"), + "OpenAPI metadata check: %zu error(s), %zu warning(s) across %zu route(s)", errors, warnings, + route_registry_->size()); if (errors > 0) { - RCLCPP_ERROR(rclcpp::get_logger("rest_server"), - "Routes above publish incomplete OpenAPI metadata; generated clients will be wrong for them"); + MEDKIT_LOG_ERROR(log::Logger("rest_server"), + "Routes above publish incomplete OpenAPI metadata; generated clients will be wrong for them"); } } diff --git a/src/ros2_medkit_gateway/src/openapi/capability_generator.cpp b/src/ros2_medkit_gateway/src/openapi/capability_generator.cpp index 356f3181c..7db0f893f 100644 --- a/src/ros2_medkit_gateway/src/openapi/capability_generator.cpp +++ b/src/ros2_medkit_gateway/src/openapi/capability_generator.cpp @@ -29,6 +29,7 @@ #include "ros2_medkit_gateway/core/http/http_utils.hpp" #include "ros2_medkit_gateway/core/http/member_qualified_id.hpp" #include "ros2_medkit_gateway/core/http/operation_item_id.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include "ros2_medkit_gateway/core/models/entity_types.hpp" #include "ros2_medkit_gateway/core/openapi/document_checks.hpp" #include "ros2_medkit_gateway/core/plugins/plugin_manager.hpp" @@ -344,10 +345,10 @@ nlohmann::json CapabilityGenerator::generate_root() const { // silently replacing the gateway's description of that path would hide it. for (const auto & [path, item] : extension_paths.items()) { if (registry_paths.contains(path)) { - RCLCPP_WARN(handlers::HandlerContext::logger(), - "Plugin describes path '%s', which the gateway already documents; keeping the gateway's " - "description. The plugin's route is still mounted - check it is not shadowing a gateway route.", - path.c_str()); + MEDKIT_LOG_WARN(handlers::HandlerContext::logger(), + "Plugin describes path '%s', which the gateway already documents; keeping the gateway's " + "description. The plugin's route is still mounted - check it is not shadowing a gateway route.", + path.c_str()); continue; } builder.add_paths(nlohmann::json{{path, item}}); @@ -391,10 +392,10 @@ nlohmann::json CapabilityGenerator::generate_root() const { for (const auto & name : orphans) { names += (names.empty() ? "" : ", ") + name; } - RCLCPP_WARN(handlers::HandlerContext::logger(), - "OpenAPI document ships %zu schema(s) no operation can reach: %s. Bind each to the route " - "that returns it, or drop it from dto::AllDtos.", - orphans.size(), names.c_str()); + MEDKIT_LOG_WARN(handlers::HandlerContext::logger(), + "OpenAPI document ships %zu schema(s) no operation can reach: %s. Bind each to the route " + "that returns it, or drop it from dto::AllDtos.", + orphans.size(), names.c_str()); } return document; diff --git a/src/ros2_medkit_gateway/src/plugins/plugin_loader.cpp b/src/ros2_medkit_gateway/src/plugins/plugin_loader.cpp index 370cc3138..b98aada94 100644 --- a/src/ros2_medkit_gateway/src/plugins/plugin_loader.cpp +++ b/src/ros2_medkit_gateway/src/plugins/plugin_loader.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ros2_medkit_gateway/core/plugins/plugin_loader.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include "ros2_medkit_gateway/core/plugins/plugin_types.hpp" #include "ros2_medkit_gateway/core/providers/data_provider.hpp" @@ -24,7 +25,6 @@ #include "ros2_medkit_gateway/core/providers/update_provider.hpp" #include -#include #include @@ -205,11 +205,11 @@ tl::expected PluginLoader::load(const std: try { result.update_provider = update_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_update_provider threw in %s: %s", plugin_path.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_update_provider threw in %s: %s", plugin_path.c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_update_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_update_provider threw unknown exception in %s", + plugin_path.c_str()); } } @@ -219,11 +219,11 @@ tl::expected PluginLoader::load(const std: try { result.introspection_provider = introspection_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_introspection_provider threw in %s: %s", - plugin_path.c_str(), e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_introspection_provider threw in %s: %s", plugin_path.c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_introspection_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_introspection_provider threw unknown exception in %s", + plugin_path.c_str()); } } @@ -233,11 +233,10 @@ tl::expected PluginLoader::load(const std: try { result.log_provider = log_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_log_provider threw in %s: %s", plugin_path.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_log_provider threw in %s: %s", plugin_path.c_str(), e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_log_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_log_provider threw unknown exception in %s", + plugin_path.c_str()); } } @@ -247,11 +246,11 @@ tl::expected PluginLoader::load(const std: try { result.script_provider = script_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_script_provider threw in %s: %s", plugin_path.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_script_provider threw in %s: %s", plugin_path.c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_script_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_script_provider threw unknown exception in %s", + plugin_path.c_str()); } } @@ -261,11 +260,10 @@ tl::expected PluginLoader::load(const std: try { result.data_provider = data_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_data_provider threw in %s: %s", plugin_path.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_data_provider threw in %s: %s", plugin_path.c_str(), e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_data_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_data_provider threw unknown exception in %s", + plugin_path.c_str()); } } @@ -275,11 +273,11 @@ tl::expected PluginLoader::load(const std: try { result.operation_provider = operation_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_operation_provider threw in %s: %s", plugin_path.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_operation_provider threw in %s: %s", plugin_path.c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_operation_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_operation_provider threw unknown exception in %s", + plugin_path.c_str()); } } @@ -289,11 +287,11 @@ tl::expected PluginLoader::load(const std: try { result.fault_provider = fault_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_fault_provider threw in %s: %s", plugin_path.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_fault_provider threw in %s: %s", plugin_path.c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_fault_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_fault_provider threw unknown exception in %s", + plugin_path.c_str()); } } @@ -303,11 +301,11 @@ tl::expected PluginLoader::load(const std: try { result.lifecycle_provider = lifecycle_fn(raw_plugin); } catch (const std::exception & e) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_lifecycle_provider threw in %s: %s", plugin_path.c_str(), - e.what()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_lifecycle_provider threw in %s: %s", plugin_path.c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(rclcpp::get_logger("plugin_loader"), "get_lifecycle_provider threw unknown exception in %s", - plugin_path.c_str()); + MEDKIT_LOG_WARN(log::Logger("plugin_loader"), "get_lifecycle_provider threw unknown exception in %s", + plugin_path.c_str()); } } diff --git a/src/ros2_medkit_gateway/src/plugins/plugin_manager.cpp b/src/ros2_medkit_gateway/src/plugins/plugin_manager.cpp index 47b8e7036..405e8296d 100644 --- a/src/ros2_medkit_gateway/src/plugins/plugin_manager.cpp +++ b/src/ros2_medkit_gateway/src/plugins/plugin_manager.cpp @@ -13,11 +13,11 @@ // limitations under the License. #include "ros2_medkit_gateway/core/plugins/plugin_manager.hpp" +#include "ros2_medkit_gateway/core/logging.hpp" #include #include -#include #include #include @@ -28,24 +28,25 @@ namespace ros2_medkit_gateway { namespace { -auto logger() { - return rclcpp::get_logger("plugin_manager"); +const log::Logger & logger() { + static const log::Logger instance("plugin_manager"); + return instance; } } // namespace void PluginManager::setup_plugin_logging(GatewayPlugin & plugin) { std::string plugin_name = plugin.name(); plugin.set_logger([plugin_name](PluginLogLevel level, const std::string & msg) { - auto log = rclcpp::get_logger("plugin." + plugin_name); + const log::Logger plugin_logger("plugin." + plugin_name); switch (level) { case PluginLogLevel::kInfo: - RCLCPP_INFO(log, "%s", msg.c_str()); + MEDKIT_LOG_INFO(plugin_logger, "%s", msg.c_str()); break; case PluginLogLevel::kWarn: - RCLCPP_WARN(log, "%s", msg.c_str()); + MEDKIT_LOG_WARN(plugin_logger, "%s", msg.c_str()); break; case PluginLogLevel::kError: - RCLCPP_ERROR(log, "%s", msg.c_str()); + MEDKIT_LOG_ERROR(plugin_logger, "%s", msg.c_str()); break; } }); @@ -89,7 +90,7 @@ void PluginManager::add_plugin(std::unique_ptr plugin) { if (!first_update_provider_) { first_update_provider_ = lp.update_provider; } else { - RCLCPP_WARN(logger(), "Multiple UpdateProvider plugins loaded - ignoring '%s'", plugin->name().c_str()); + MEDKIT_LOG_WARN(logger(), "Multiple UpdateProvider plugins loaded - ignoring '%s'", plugin->name().c_str()); } } @@ -98,7 +99,7 @@ void PluginManager::add_plugin(std::unique_ptr plugin) { if (!first_log_provider_) { first_log_provider_ = lp.log_provider; } else { - RCLCPP_DEBUG(logger(), "LogProvider plugin '%s' registered as observer only", plugin->name().c_str()); + MEDKIT_LOG_DEBUG(logger(), "LogProvider plugin '%s' registered as observer only", plugin->name().c_str()); } } @@ -107,7 +108,7 @@ void PluginManager::add_plugin(std::unique_ptr plugin) { if (!first_script_provider_) { first_script_provider_ = lp.script_provider; } else { - RCLCPP_WARN(logger(), "Multiple ScriptProvider plugins loaded - ignoring '%s'", plugin->name().c_str()); + MEDKIT_LOG_WARN(logger(), "Multiple ScriptProvider plugins loaded - ignoring '%s'", plugin->name().c_str()); } } @@ -122,7 +123,7 @@ size_t PluginManager::load_plugins(const std::vector & configs) { for (const auto & cfg : configs) { auto result = PluginLoader::load(cfg.path); if (result) { - RCLCPP_INFO(logger(), "Loaded plugin '%s' from %s", result->plugin->name().c_str(), cfg.path.c_str()); + MEDKIT_LOG_INFO(logger(), "Loaded plugin '%s' from %s", result->plugin->name().c_str(), cfg.path.c_str()); LoadedPlugin lp; lp.config = cfg.config; @@ -151,8 +152,8 @@ size_t PluginManager::load_plugins(const std::vector & configs) { if (!first_update_provider_) { first_update_provider_ = lp.update_provider; } else { - RCLCPP_WARN(logger(), "Multiple UpdateProvider plugins loaded - ignoring '%s'", - result->plugin->name().c_str()); + MEDKIT_LOG_WARN(logger(), "Multiple UpdateProvider plugins loaded - ignoring '%s'", + result->plugin->name().c_str()); } } @@ -161,7 +162,8 @@ size_t PluginManager::load_plugins(const std::vector & configs) { if (!first_log_provider_) { first_log_provider_ = lp.log_provider; } else { - RCLCPP_DEBUG(logger(), "LogProvider plugin '%s' registered as observer only", result->plugin->name().c_str()); + MEDKIT_LOG_DEBUG(logger(), "LogProvider plugin '%s' registered as observer only", + result->plugin->name().c_str()); } } @@ -170,8 +172,8 @@ size_t PluginManager::load_plugins(const std::vector & configs) { if (!first_script_provider_) { first_script_provider_ = lp.script_provider; } else { - RCLCPP_WARN(logger(), "Multiple ScriptProvider plugins loaded - ignoring '%s'", - result->plugin->name().c_str()); + MEDKIT_LOG_WARN(logger(), "Multiple ScriptProvider plugins loaded - ignoring '%s'", + result->plugin->name().c_str()); } } @@ -180,7 +182,7 @@ size_t PluginManager::load_plugins(const std::vector & configs) { plugins_.push_back(std::move(lp)); ++loaded; } else { - RCLCPP_ERROR(logger(), "Failed to load plugin from %s: %s", cfg.path.c_str(), result.error().c_str()); + MEDKIT_LOG_ERROR(logger(), "Failed to load plugin from %s: %s", cfg.path.c_str(), result.error().c_str()); } } return loaded; @@ -191,10 +193,11 @@ void PluginManager::disable_plugin(LoadedPlugin & lp) { try { lp.load_result.plugin->shutdown(); } catch (const std::exception & e) { - RCLCPP_WARN(logger(), "Plugin '%s' threw during shutdown(): %s", lp.load_result.plugin->name().c_str(), e.what()); + MEDKIT_LOG_WARN(logger(), "Plugin '%s' threw during shutdown(): %s", lp.load_result.plugin->name().c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(logger(), "Plugin '%s' threw unknown exception during shutdown()", - lp.load_result.plugin->name().c_str()); + MEDKIT_LOG_WARN(logger(), "Plugin '%s' threw unknown exception during shutdown()", + lp.load_result.plugin->name().c_str()); } } // Invalidate cached provider if this plugin was the source, re-scan for next @@ -279,12 +282,12 @@ void PluginManager::configure_plugins() { try { lp.load_result.plugin->configure(lp.config); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "Plugin '%s' threw during configure(): %s", lp.load_result.plugin->name().c_str(), - e.what()); + MEDKIT_LOG_ERROR(logger(), "Plugin '%s' threw during configure(): %s", lp.load_result.plugin->name().c_str(), + e.what()); disable_plugin(lp); } catch (...) { - RCLCPP_ERROR(logger(), "Plugin '%s' threw unknown exception during configure()", - lp.load_result.plugin->name().c_str()); + MEDKIT_LOG_ERROR(logger(), "Plugin '%s' threw unknown exception during configure()", + lp.load_result.plugin->name().c_str()); disable_plugin(lp); } } @@ -311,13 +314,13 @@ void PluginManager::set_context(PluginContext & context) { capture_new_sampler_collections(lp, before_collections); } catch (const std::exception & e) { capture_new_sampler_collections(lp, before_collections); - RCLCPP_ERROR(logger(), "Plugin '%s' threw during set_context(): %s - disabling", - lp.load_result.plugin->name().c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "Plugin '%s' threw during set_context(): %s - disabling", + lp.load_result.plugin->name().c_str(), e.what()); disable_plugin(lp); } catch (...) { capture_new_sampler_collections(lp, before_collections); - RCLCPP_ERROR(logger(), "Plugin '%s' threw unknown exception during set_context() - disabling", - lp.load_result.plugin->name().c_str()); + MEDKIT_LOG_ERROR(logger(), "Plugin '%s' threw unknown exception during set_context() - disabling", + lp.load_result.plugin->name().c_str()); disable_plugin(lp); } } @@ -344,12 +347,12 @@ bool PluginManager::cache_routes_locked(LoadedPlugin & lp) { lp.routes_cached = true; return true; } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "Plugin '%s' threw during get_routes(): %s - disabling", - lp.load_result.plugin->name().c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "Plugin '%s' threw during get_routes(): %s - disabling", + lp.load_result.plugin->name().c_str(), e.what()); disable_plugin(lp); } catch (...) { - RCLCPP_ERROR(logger(), "Plugin '%s' threw unknown exception during get_routes() - disabling", - lp.load_result.plugin->name().c_str()); + MEDKIT_LOG_ERROR(logger(), "Plugin '%s' threw unknown exception during get_routes() - disabling", + lp.load_result.plugin->name().c_str()); disable_plugin(lp); } return false; @@ -372,13 +375,13 @@ void PluginManager::register_routes(httplib::Server & server, const std::string PluginResponse plugin_res(&res); handler_fn(plugin_req, plugin_res); } catch (const std::exception & e) { - RCLCPP_ERROR(rclcpp::get_logger("plugin_manager"), "Plugin '%s' handler threw on %s: %s", plugin_name.c_str(), - full_pattern.c_str(), e.what()); + MEDKIT_LOG_ERROR(log::Logger("plugin_manager"), "Plugin '%s' handler threw on %s: %s", plugin_name.c_str(), + full_pattern.c_str(), e.what()); PluginResponse plugin_res(&res); plugin_res.send_error(500, ERR_PLUGIN_ERROR, "Internal plugin error"); } catch (...) { - RCLCPP_ERROR(rclcpp::get_logger("plugin_manager"), "Plugin '%s' handler threw unknown exception on %s", - plugin_name.c_str(), full_pattern.c_str()); + MEDKIT_LOG_ERROR(log::Logger("plugin_manager"), "Plugin '%s' handler threw unknown exception on %s", + plugin_name.c_str(), full_pattern.c_str()); PluginResponse plugin_res(&res); plugin_res.send_error(500, ERR_PLUGIN_ERROR, "Internal plugin error"); } @@ -393,8 +396,8 @@ void PluginManager::register_routes(httplib::Server & server, const std::string } else if (route.method == "DELETE") { server.Delete(full_pattern, httplib_handler); } else { - RCLCPP_WARN(logger(), "Plugin '%s' registered route with unknown method '%s' - skipping", - lp.load_result.plugin->name().c_str(), route.method.c_str()); + MEDKIT_LOG_WARN(logger(), "Plugin '%s' registered route with unknown method '%s' - skipping", + lp.load_result.plugin->name().c_str(), route.method.c_str()); } } } @@ -491,10 +494,11 @@ std::optional PluginManager::fetch_entity_data_via_route(const s PluginResponse plugin_res(&res); handler(plugin_req, plugin_res); } catch (const std::exception & e) { - RCLCPP_WARN(logger(), "In-process x-plc-data dispatch for entity '%s' threw: %s", entity_id.c_str(), e.what()); + MEDKIT_LOG_WARN(logger(), "In-process x-plc-data dispatch for entity '%s' threw: %s", entity_id.c_str(), e.what()); return std::nullopt; } catch (...) { - RCLCPP_WARN(logger(), "In-process x-plc-data dispatch for entity '%s' threw unknown exception", entity_id.c_str()); + MEDKIT_LOG_WARN(logger(), "In-process x-plc-data dispatch for entity '%s' threw unknown exception", + entity_id.c_str()); return std::nullopt; } @@ -521,10 +525,11 @@ void PluginManager::shutdown_all() { try { lp.load_result.plugin->shutdown(); } catch (const std::exception & e) { - RCLCPP_WARN(logger(), "Plugin '%s' threw during shutdown(): %s", lp.load_result.plugin->name().c_str(), e.what()); + MEDKIT_LOG_WARN(logger(), "Plugin '%s' threw during shutdown(): %s", lp.load_result.plugin->name().c_str(), + e.what()); } catch (...) { - RCLCPP_WARN(logger(), "Plugin '%s' threw unknown exception during shutdown()", - lp.load_result.plugin->name().c_str()); + MEDKIT_LOG_WARN(logger(), "Plugin '%s' threw unknown exception during shutdown()", + lp.load_result.plugin->name().c_str()); } // The plugin instance itself is freed later, in ~PluginManager()'s // plugins_.clear() - but ResourceSamplerRegistry is owned by GatewayNode @@ -614,8 +619,8 @@ void PluginManager::register_entity_ownership(const std::string & plugin_name, for (const auto & eid : entity_ids) { auto it = entity_ownership_.find(eid); if (it != entity_ownership_.end() && it->second != plugin_name) { - RCLCPP_WARN(logger(), "Entity '%s' ownership transferred from plugin '%s' to '%s'", eid.c_str(), - it->second.c_str(), plugin_name.c_str()); + MEDKIT_LOG_WARN(logger(), "Entity '%s' ownership transferred from plugin '%s' to '%s'", eid.c_str(), + it->second.c_str(), plugin_name.c_str()); } entity_ownership_[eid] = plugin_name; } @@ -726,11 +731,11 @@ std::vector PluginManager::collect_route_description try { all_descriptions.push_back(fn()); } catch (const std::exception & e) { - RCLCPP_WARN(logger(), "Plugin '%s' threw in describe_plugin_routes(): %s", lp.load_result.plugin->name().c_str(), - e.what()); + MEDKIT_LOG_WARN(logger(), "Plugin '%s' threw in describe_plugin_routes(): %s", + lp.load_result.plugin->name().c_str(), e.what()); } catch (...) { - RCLCPP_WARN(logger(), "Plugin '%s' threw unknown exception in describe_plugin_routes()", - lp.load_result.plugin->name().c_str()); + MEDKIT_LOG_WARN(logger(), "Plugin '%s' threw unknown exception in describe_plugin_routes()", + lp.load_result.plugin->name().c_str()); } } diff --git a/src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp b/src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp new file mode 100644 index 000000000..4b49a1e51 --- /dev/null +++ b/src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp @@ -0,0 +1,33 @@ +// Copyright 2026 bburda +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ros2_medkit_gateway/ros2/ros2_log_sink.hpp" + +#include + +namespace ros2_medkit_gateway { + +bool Ros2LogSink::enabled(std::string_view logger, log::Level level) const { + return rcutils_logging_logger_is_enabled_for(std::string(logger).c_str(), static_cast(level)); +} + +void Ros2LogSink::write(std::string_view logger, log::Level level, std::string_view message, + const log::Location & location) { + const rcutils_log_location_t rcutils_location = {location.function, location.file, + static_cast(location.line)}; + rcutils_log(&rcutils_location, static_cast(level), std::string(logger).c_str(), "%.*s", + static_cast(message.size()), message.data()); +} + +} // namespace ros2_medkit_gateway diff --git a/src/ros2_medkit_gateway/src/script_manager.cpp b/src/ros2_medkit_gateway/src/script_manager.cpp index f9f6357fb..931be3ffe 100644 --- a/src/ros2_medkit_gateway/src/script_manager.cpp +++ b/src/ros2_medkit_gateway/src/script_manager.cpp @@ -13,14 +13,14 @@ // limitations under the License. #include "ros2_medkit_gateway/core/managers/script_manager.hpp" - -#include +#include "ros2_medkit_gateway/core/logging.hpp" namespace ros2_medkit_gateway { namespace { -auto logger() { - return rclcpp::get_logger("script_manager"); +const log::Logger & logger() { + static const log::Logger instance("script_manager"); + return instance; } } // namespace @@ -40,11 +40,11 @@ ScriptManager::list_scripts(const std::string & entity_id) { try { return backend_->list_scripts(entity_id); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "list_scripts failed for entity '%s': %s", entity_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "list_scripts failed for entity '%s': %s", entity_id.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), "list_scripts failed for entity '%s': unknown exception", entity_id.c_str()); + MEDKIT_LOG_ERROR(logger(), "list_scripts failed for entity '%s': unknown exception", entity_id.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } @@ -57,13 +57,13 @@ tl::expected ScriptManager::get_script(const try { return backend_->get_script(entity_id, script_id); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "get_script failed for entity '%s', script '%s': %s", entity_id.c_str(), script_id.c_str(), - e.what()); + MEDKIT_LOG_ERROR(logger(), "get_script failed for entity '%s', script '%s': %s", entity_id.c_str(), + script_id.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), "get_script failed for entity '%s', script '%s': unknown exception", entity_id.c_str(), - script_id.c_str()); + MEDKIT_LOG_ERROR(logger(), "get_script failed for entity '%s', script '%s': unknown exception", entity_id.c_str(), + script_id.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } @@ -77,13 +77,13 @@ ScriptManager::upload_script(const std::string & entity_id, const std::string & try { return backend_->upload_script(entity_id, filename, content, metadata); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "upload_script failed for entity '%s', file '%s': %s", entity_id.c_str(), filename.c_str(), - e.what()); + MEDKIT_LOG_ERROR(logger(), "upload_script failed for entity '%s', file '%s': %s", entity_id.c_str(), + filename.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), "upload_script failed for entity '%s', file '%s': unknown exception", entity_id.c_str(), - filename.c_str()); + MEDKIT_LOG_ERROR(logger(), "upload_script failed for entity '%s', file '%s': unknown exception", entity_id.c_str(), + filename.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } @@ -96,13 +96,13 @@ tl::expected ScriptManager::delete_script(const st try { return backend_->delete_script(entity_id, script_id); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "delete_script failed for entity '%s', script '%s': %s", entity_id.c_str(), - script_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "delete_script failed for entity '%s', script '%s': %s", entity_id.c_str(), + script_id.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), "delete_script failed for entity '%s', script '%s': unknown exception", entity_id.c_str(), - script_id.c_str()); + MEDKIT_LOG_ERROR(logger(), "delete_script failed for entity '%s', script '%s': unknown exception", + entity_id.c_str(), script_id.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } @@ -116,13 +116,13 @@ tl::expected ScriptManager::start_executi try { return backend_->start_execution(entity_id, script_id, request); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "start_execution failed for entity '%s', script '%s': %s", entity_id.c_str(), - script_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "start_execution failed for entity '%s', script '%s': %s", entity_id.c_str(), + script_id.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), "start_execution failed for entity '%s', script '%s': unknown exception", entity_id.c_str(), - script_id.c_str()); + MEDKIT_LOG_ERROR(logger(), "start_execution failed for entity '%s', script '%s': unknown exception", + entity_id.c_str(), script_id.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } @@ -136,13 +136,13 @@ tl::expected ScriptManager::get_execution try { return backend_->get_execution(entity_id, script_id, execution_id); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "get_execution failed for entity '%s', script '%s', execution '%s': %s", entity_id.c_str(), - script_id.c_str(), execution_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "get_execution failed for entity '%s', script '%s', execution '%s': %s", + entity_id.c_str(), script_id.c_str(), execution_id.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), "get_execution failed for entity '%s', script '%s', execution '%s': unknown exception", - entity_id.c_str(), script_id.c_str(), execution_id.c_str()); + MEDKIT_LOG_ERROR(logger(), "get_execution failed for entity '%s', script '%s', execution '%s': unknown exception", + entity_id.c_str(), script_id.c_str(), execution_id.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } @@ -157,14 +157,14 @@ tl::expected ScriptManager::control_execu try { return backend_->control_execution(entity_id, script_id, execution_id, action); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "control_execution('%s') failed for entity '%s', script '%s', execution '%s': %s", - action.c_str(), entity_id.c_str(), script_id.c_str(), execution_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "control_execution('%s') failed for entity '%s', script '%s', execution '%s': %s", + action.c_str(), entity_id.c_str(), script_id.c_str(), execution_id.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), - "control_execution('%s') failed for entity '%s', script '%s', execution '%s': unknown exception", - action.c_str(), entity_id.c_str(), script_id.c_str(), execution_id.c_str()); + MEDKIT_LOG_ERROR(logger(), + "control_execution('%s') failed for entity '%s', script '%s', execution '%s': unknown exception", + action.c_str(), entity_id.c_str(), script_id.c_str(), execution_id.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } @@ -178,13 +178,14 @@ tl::expected ScriptManager::delete_execution(const try { return backend_->delete_execution(entity_id, script_id, execution_id); } catch (const std::exception & e) { - RCLCPP_ERROR(logger(), "delete_execution failed for entity '%s', script '%s', execution '%s': %s", - entity_id.c_str(), script_id.c_str(), execution_id.c_str(), e.what()); + MEDKIT_LOG_ERROR(logger(), "delete_execution failed for entity '%s', script '%s', execution '%s': %s", + entity_id.c_str(), script_id.c_str(), execution_id.c_str(), e.what()); return tl::make_unexpected( ScriptBackendErrorInfo{ScriptBackendError::Internal, std::string("Backend error: ") + e.what()}); } catch (...) { - RCLCPP_ERROR(logger(), "delete_execution failed for entity '%s', script '%s', execution '%s': unknown exception", - entity_id.c_str(), script_id.c_str(), execution_id.c_str()); + MEDKIT_LOG_ERROR(logger(), + "delete_execution failed for entity '%s', script '%s', execution '%s': unknown exception", + entity_id.c_str(), script_id.c_str(), execution_id.c_str()); return tl::make_unexpected(ScriptBackendErrorInfo{ScriptBackendError::Internal, "Unknown backend error"}); } } diff --git a/src/ros2_medkit_gateway/test/demo_nodes/test_gateway_plugin.cpp b/src/ros2_medkit_gateway/test/demo_nodes/test_gateway_plugin.cpp index 7766a87bf..23cdcb0a7 100644 --- a/src/ros2_medkit_gateway/test/demo_nodes/test_gateway_plugin.cpp +++ b/src/ros2_medkit_gateway/test/demo_nodes/test_gateway_plugin.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "ros2_medkit_gateway/core/logging.hpp" #include "ros2_medkit_gateway/core/plugins/gateway_plugin.hpp" #include "ros2_medkit_gateway/core/plugins/plugin_context.hpp" #include "ros2_medkit_gateway/core/plugins/plugin_types.hpp" @@ -42,6 +43,7 @@ class TestGatewayPlugin : public GatewayPlugin, public UpdateProvider, public In } void configure(const nlohmann::json & /*config*/) override { + MEDKIT_LOG_INFO(log::Logger("test_gateway_plugin"), "test_gateway_plugin configured"); } void set_context(PluginContext & context) override { diff --git a/src/ros2_medkit_gateway/test/test_handler_context.cpp b/src/ros2_medkit_gateway/test/test_handler_context.cpp index aebeb1094..5ed7763c4 100644 --- a/src/ros2_medkit_gateway/test/test_handler_context.cpp +++ b/src/ros2_medkit_gateway/test/test_handler_context.cpp @@ -65,10 +65,9 @@ using namespace ros2_medkit_gateway::handlers; // ============================================================================= TEST(HandlerContextStaticTest, LoggerReturnsValidLogger) { - auto logger = HandlerContext::logger(); + const auto & logger = HandlerContext::logger(); // Just verify it doesn't throw and returns a valid logger name - EXPECT_NE(logger.get_name(), nullptr); - EXPECT_GT(strlen(logger.get_name()), 0); + EXPECT_EQ(logger.name(), "rest_server"); } // ============================================================================= diff --git a/src/ros2_medkit_integration_tests/CMakeLists.txt b/src/ros2_medkit_integration_tests/CMakeLists.txt index 27781d549..501072fcd 100644 --- a/src/ros2_medkit_integration_tests/CMakeLists.txt +++ b/src/ros2_medkit_integration_tests/CMakeLists.txt @@ -276,6 +276,7 @@ if(BUILD_TESTING) test_aggregator_only_configurations test_leaf_collision_aggregation test_startup_param_clamp_warnings + test_rest_logger_names test_aggregation_time_budgets test_peer_failure_reasons test_relay_peer_credential) diff --git a/src/ros2_medkit_integration_tests/test/features/test_rest_logger_names.test.py b/src/ros2_medkit_integration_tests/test/features/test_rest_logger_names.test.py new file mode 100644 index 000000000..d5440f326 --- /dev/null +++ b/src/ros2_medkit_integration_tests/test/features/test_rest_logger_names.test.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# Copyright 2026 bburda +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Log lines keep their logger names, and per-name level control still works. + +Three gateways run side by side: + +- ``gw_debug`` is launched with ``--log-level rest_server:=debug`` only. +- ``gw_plain`` is launched without a flag and loads a MODULE plugin that logs + one line when it is configured. +- ``gw_format`` prints the call site through ``RCUTILS_CONSOLE_OUTPUT_FORMAT``. + +Presence is checked while the gateways run. Absence is checked after shutdown, +over each process's whole output. +""" + +import os +import re +import time +import unittest + +from ament_index_python.packages import get_package_prefix +from launch import LaunchDescription +import launch_testing +import launch_testing.actions +import requests + +from ros2_medkit_test_utils.constants import ( + ALLOWED_EXIT_CODES, + API_BASE_PATH, + get_test_domain_id, + get_test_port, + get_time_scale, +) +from ros2_medkit_test_utils.gateway_test_case import GatewayTestCase +from ros2_medkit_test_utils.launch_helpers import create_gateway_node + +OUTPUT_TIMEOUT = 20.0 + + +def _domain(offset): + return {'ROS_DOMAIN_ID': str(get_test_domain_id(offset))} + + +def generate_test_description(): + plugin_path = os.path.join( + get_package_prefix('ros2_medkit_gateway'), + 'lib', 'ros2_medkit_gateway', 'libtest_gateway_plugin.so', + ) + gw_debug = create_gateway_node( + extra_env=_domain(0), + ros_arguments=['--log-level', 'rest_server:=debug'], + ) + gw_plain = create_gateway_node( + port=get_test_port(1), + name='gateway_plain_log_level', + extra_env=_domain(1), + extra_params={ + 'plugins': ['test_plugin'], + 'plugins.test_plugin.path': plugin_path, + }, + ) + gw_format = create_gateway_node( + port=get_test_port(2), + name='gateway_location_format', + extra_env={ + **_domain(2), + 'RCUTILS_CONSOLE_OUTPUT_FORMAT': + '[{severity}] [{name}] {file_name}:{line_number} {message}', + }, + ) + return ( + LaunchDescription([ + gw_debug, + gw_plain, + gw_format, + launch_testing.actions.ReadyToTest(), + ]), + {'gw_debug': gw_debug, 'gw_plain': gw_plain, 'gw_format': gw_format}, + ) + + +def _line_pattern(level, logger, text): + return re.compile( + r'^\[' + level + r'\] \[[0-9.]+\] \[' + logger + r'\]: ' + re.escape(text), + re.MULTILINE, + ) + + +def _output(proc_output, process): + return ''.join( + chunk.text.decode(errors='replace') for chunk in proc_output[process] + ) + + +def _wait_for_line(proc_output, process, pattern): + deadline = time.monotonic() + OUTPUT_TIMEOUT * get_time_scale() + while time.monotonic() < deadline: + if pattern.search(_output(proc_output, process)): + return True + time.sleep(0.2) + return False + + +def _exercise(base_url): + """Drive requests that log a rest_server DEBUG line, plus a health and a 404.""" + assert requests.get(f'{base_url}/health', timeout=5).status_code == 200 + components = requests.get(f'{base_url}/components', timeout=5).json()['items'] + component_id = components[0]['id'] + response = requests.get(f'{base_url}/components/{component_id}/operations', timeout=5) + assert response.status_code == 200, response.text + assert requests.get(f'{base_url}/no_such_route', timeout=5).status_code == 404 + + +def _wait_healthy(base_url): + deadline = time.monotonic() + 30.0 * get_time_scale() + while time.monotonic() < deadline: + try: + if requests.get(f'{base_url}/health', timeout=2).status_code == 200: + return + except requests.exceptions.RequestException: + pass + time.sleep(0.2) + raise AssertionError(f'{base_url} not healthy') + + +DEBUG_REST = re.compile(r'\[DEBUG\] \[[0-9.]+\] \[rest_server\]') +DEBUG_HTTP = re.compile(r'\[DEBUG\] \[[0-9.]+\] \[http_server\]') +LISTING_LINE = _line_pattern('DEBUG', 'rest_server', 'Listing operations for component') +STARTUP_LINES = ( + ('rest_server', 'HTTP request thread pool bounded'), + ('rest_server', 'OpenAPI metadata check'), + ('http_server', 'Starting HTTP server'), +) + + +class TestRestLoggerNames(GatewayTestCase): + """Logger names, per-name levels, call sites and plugin lines on the console.""" + + MIN_EXPECTED_APPS = 0 + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.plain_url = f'http://localhost:{get_test_port(1)}{API_BASE_PATH}' + _wait_healthy(cls.plain_url) + + def test_debug_line_appears_when_enabled_by_name(self, proc_output, gw_debug): + """rest_server DEBUG lines print when raised by name.""" + _exercise(self.BASE_URL) + self.assertTrue( + _wait_for_line(proc_output, gw_debug, LISTING_LINE), + _output(proc_output, gw_debug), + ) + + def test_info_lines_keep_their_logger_names(self, proc_output, gw_debug, gw_plain): + """Startup INFO lines carry the same names with and without the flag.""" + for process in (gw_debug, gw_plain): + for logger, text in STARTUP_LINES: + self.assertTrue( + _wait_for_line(proc_output, process, _line_pattern('INFO', logger, text)), + f'{logger}: {text}\n' + _output(proc_output, process), + ) + + def test_plain_gateway_serves_requests(self, proc_output, gw_plain): + """Requests reach a gateway launched without the flag.""" + _exercise(self.plain_url) + self.assertTrue( + _wait_for_line( + proc_output, gw_plain, + _line_pattern('INFO', 'rest_server', 'OpenAPI metadata check'), + ), + ) + + def test_plugin_line_goes_through_the_process_sink(self, proc_output, gw_plain): + """A line logged by a MODULE plugin is formatted by rcutils, with a ROS timestamp.""" + self.assertTrue( + _wait_for_line( + proc_output, gw_plain, + _line_pattern('INFO', 'test_gateway_plugin', 'test_gateway_plugin configured'), + ), + _output(proc_output, gw_plain), + ) + + def test_call_site_reaches_the_console_format(self, proc_output, gw_format): + """File name and line number of the call site reach the console format.""" + pattern = re.compile( + r'^\[INFO\] \[rest_server\] \S*[^/\s]+\.cpp:([0-9]+) ' + r'HTTP request thread pool bounded', + re.MULTILINE, + ) + self.assertTrue( + _wait_for_line(proc_output, gw_format, pattern), _output(proc_output, gw_format), + ) + match = pattern.search(_output(proc_output, gw_format)) + self.assertGreater(int(match.group(1)), 0) + + +@launch_testing.post_shutdown_test() +class TestAfterShutdown(unittest.TestCase): + + def test_exit_codes(self, proc_info): + """All gateways exit cleanly.""" + launch_testing.asserts.assertExitCodes(proc_info, allowable_exit_codes=ALLOWED_EXIT_CODES) + + def test_only_the_named_logger_prints_debug(self, proc_output, gw_debug): + """Raising rest_server leaves http_server at INFO.""" + text = _output(proc_output, gw_debug) + self.assertRegex(text, DEBUG_REST) + self.assertNotRegex(text, DEBUG_HTTP) + + def test_no_debug_lines_without_the_flag(self, proc_output, gw_plain): + """A gateway launched without the flag prints no DEBUG line for these names.""" + text = _output(proc_output, gw_plain) + self.assertNotRegex(text, DEBUG_REST) + self.assertNotRegex(text, DEBUG_HTTP) From e6b4fbc58b367adbd2f70e71fbeae129f7ee8dcd Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Fri, 23 Oct 2026 21:37:39 +0200 Subject: [PATCH 2/2] perf(gateway): read the log sink through a raw pointer A log call took a std::shared_ptr copy of the sink through std::atomic_load, which locks a mutex from a process-wide pool and changes the reference count, and the call to enabled() did it a second time. The slot now holds a std::atomic that the log path reads, and set_sink keeps every installed sink alive until the process exits, so a thread that is logging never uses a freed sink. Sink::enabled and Sink::write take the logger name as const std::string &, so the ROS sink passes c_str() to rcutils without building a string. --- .../ros2_medkit_gateway/core/logging.hpp | 31 ++++++++++++++-- .../ros2/ros2_log_sink.hpp | 4 +- src/ros2_medkit_gateway/src/core/logging.cpp | 37 ++++++++++++------- .../src/ros2/ros2_log_sink.cpp | 10 ++--- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp index b2ed4ab9a..ed55c9e60 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp @@ -14,9 +14,12 @@ #pragma once +#include #include +#include #include #include +#include namespace ros2_medkit_gateway::log { @@ -40,13 +43,14 @@ class Sink { Sink & operator=(Sink &&) = delete; virtual ~Sink() = default; - virtual bool enabled(std::string_view logger, Level level) const = 0; - virtual void write(std::string_view logger, Level level, std::string_view message, const Location & location) = 0; + virtual bool enabled(const std::string & logger, Level level) const = 0; + virtual void write(const std::string & logger, Level level, std::string_view message, const Location & location) = 0; }; /// Replaces the active sink. Call it once at startup, before other threads log. /// A null sink restores the default sink, which writes "[LEVEL] [name]: message" -/// to stderr for INFO and above. +/// to stderr for INFO and above. Every installed sink stays alive until the process +/// exits, because a thread that is logging may still use the sink it read before the call. void set_sink(std::shared_ptr sink); /// A named log source. Cheap to copy. @@ -65,7 +69,26 @@ class Logger { namespace detail { /// Process-wide sink slot. Default visibility, so a plugin that links this code /// binds to the executable's slot. -__attribute__((visibility("default"))) std::shared_ptr & sink_slot(); +struct SinkSlot { + SinkSlot(); + SinkSlot(const SinkSlot &) = delete; + SinkSlot & operator=(const SinkSlot &) = delete; + SinkSlot(SinkSlot &&) = delete; + SinkSlot & operator=(SinkSlot &&) = delete; + ~SinkSlot() = default; + + /// Makes @p sink the active sink and keeps it alive for the process. + void install(std::shared_ptr sink); + + /// The sink the log calls use. Never null. + std::atomic active{nullptr}; + + private: + std::mutex mutex_; + std::vector> installed_; +}; + +__attribute__((visibility("default"))) SinkSlot & sink_slot(); void emit(const Logger & logger, Level level, const Location & location, const char * fmt, ...) __attribute__((format(printf, 4, 5))); diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp index 834ab3d3e..76d5c7470 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp @@ -25,8 +25,8 @@ namespace ros2_medkit_gateway { /// control (`--log-level :=debug`) and `/rosout` behave as with rclcpp. class Ros2LogSink : public log::Sink { public: - bool enabled(std::string_view logger, log::Level level) const override; - void write(std::string_view logger, log::Level level, std::string_view message, + bool enabled(const std::string & logger, log::Level level) const override; + void write(const std::string & logger, log::Level level, std::string_view message, const log::Location & location) override; }; diff --git a/src/ros2_medkit_gateway/src/core/logging.cpp b/src/ros2_medkit_gateway/src/core/logging.cpp index 6a5071ff1..8028171f2 100644 --- a/src/ros2_medkit_gateway/src/core/logging.cpp +++ b/src/ros2_medkit_gateway/src/core/logging.cpp @@ -14,7 +14,6 @@ #include "ros2_medkit_gateway/core/logging.hpp" -#include #include #include #include @@ -42,17 +41,18 @@ const char * level_name(Level level) { class StderrSink : public Sink { public: - bool enabled(std::string_view /*logger*/, Level level) const override { + bool enabled(const std::string & /*logger*/, Level level) const override { return level >= Level::kInfo; } - void write(std::string_view logger, Level level, std::string_view message, const Location & /*location*/) override { - std::fprintf(stderr, "[%s] [%.*s]: %.*s\n", level_name(level), static_cast(logger.size()), logger.data(), - static_cast(message.size()), message.data()); + void write(const std::string & logger, Level level, std::string_view message, + const Location & /*location*/) override { + std::fprintf(stderr, "[%s] [%s]: %.*s\n", level_name(level), logger.c_str(), static_cast(message.size()), + message.data()); } }; -std::shared_ptr current_sink() { - return std::atomic_load(&detail::sink_slot()); +Sink & current_sink() { + return *detail::sink_slot().active.load(std::memory_order_acquire); } } // namespace @@ -61,20 +61,31 @@ void set_sink(std::shared_ptr sink) { if (!sink) { sink = std::make_shared(); } - std::atomic_store(&detail::sink_slot(), std::move(sink)); + detail::sink_slot().install(std::move(sink)); } Logger::Logger(std::string name) : name_(std::move(name)) { } bool Logger::enabled(Level level) const { - return current_sink()->enabled(name_, level); + return current_sink().enabled(name_, level); } namespace detail { -std::shared_ptr & sink_slot() { - static std::shared_ptr slot = std::make_shared(); +SinkSlot::SinkSlot() { + install(std::make_shared()); +} + +void SinkSlot::install(std::shared_ptr sink) { + std::lock_guard lock(mutex_); + Sink * raw = sink.get(); + installed_.push_back(std::move(sink)); + active.store(raw, std::memory_order_release); +} + +SinkSlot & sink_slot() { + static SinkSlot slot; return slot; } @@ -90,8 +101,8 @@ void emit(const Logger & logger, Level level, const Location & location, const c std::vsnprintf(buffer.data(), buffer.size(), fmt, args); } va_end(args); - current_sink()->write(logger.name(), level, - std::string_view(buffer.data(), needed > 0 ? static_cast(needed) : 0), location); + current_sink().write(logger.name(), level, + std::string_view(buffer.data(), needed > 0 ? static_cast(needed) : 0), location); } } // namespace detail diff --git a/src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp b/src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp index 4b49a1e51..30a9db9ef 100644 --- a/src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp +++ b/src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp @@ -18,16 +18,16 @@ namespace ros2_medkit_gateway { -bool Ros2LogSink::enabled(std::string_view logger, log::Level level) const { - return rcutils_logging_logger_is_enabled_for(std::string(logger).c_str(), static_cast(level)); +bool Ros2LogSink::enabled(const std::string & logger, log::Level level) const { + return rcutils_logging_logger_is_enabled_for(logger.c_str(), static_cast(level)); } -void Ros2LogSink::write(std::string_view logger, log::Level level, std::string_view message, +void Ros2LogSink::write(const std::string & logger, log::Level level, std::string_view message, const log::Location & location) { const rcutils_log_location_t rcutils_location = {location.function, location.file, static_cast(location.line)}; - rcutils_log(&rcutils_location, static_cast(level), std::string(logger).c_str(), "%.*s", - static_cast(message.size()), message.data()); + rcutils_log(&rcutils_location, static_cast(level), logger.c_str(), "%.*s", static_cast(message.size()), + message.data()); } } // namespace ros2_medkit_gateway