|
| 1 | +// Copyright 2026 mfaferek93, bburda |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#pragma once |
| 16 | + |
| 17 | +#include <map> |
| 18 | +#include <memory> |
| 19 | +#include <mutex> |
| 20 | +#include <string> |
| 21 | +#include <vector> |
| 22 | + |
| 23 | +#include "rclcpp/rclcpp.hpp" |
| 24 | +#include "rcl_interfaces/msg/log.hpp" |
| 25 | +#include "ros2_medkit_fault_reporter/fault_reporter.hpp" |
| 26 | + |
| 27 | +namespace ros2_medkit_log_bridge { |
| 28 | + |
| 29 | +/// Bridge node that promotes ROS 2 /rosout log entries to FaultManager faults. |
| 30 | +/// |
| 31 | +/// Subscribes to /rosout (rcl_interfaces/msg/Log) and forwards entries at or |
| 32 | +/// above a configurable severity floor to the FaultManager, attributing each |
| 33 | +/// fault to the originating node (the Log.name field) via a per-source |
| 34 | +/// FaultReporter. Drop-in compat adapter, same category as |
| 35 | +/// ros2_medkit_diagnostic_bridge: native FaultReporter instrumentation stays |
| 36 | +/// the canonical path; this bridge is the fallback for nodes that only log. |
| 37 | +/// Level mapping and the WARN-as-PREFAILED caveat are documented in README.md. |
| 38 | +/// |
| 39 | +/// Hard limitation by construction: only sees rclcpp logs that reach /rosout |
| 40 | +/// from a still-alive node. Console-only loggers and crash-before-flush are out |
| 41 | +/// of reach. |
| 42 | +class LogBridgeNode : public rclcpp::Node { |
| 43 | + public: |
| 44 | + explicit LogBridgeNode(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()); |
| 45 | + |
| 46 | + /// Map an rcl_interfaces/msg/Log level to a Fault severity. |
| 47 | + /// Returns false when the level is below the floor / not promotable. |
| 48 | + static bool map_level_to_severity(uint8_t log_level, uint8_t severity_floor, uint8_t * severity_out); |
| 49 | + |
| 50 | + /// Auto-generate a stable fault code from the originating node name and the |
| 51 | + /// log message. Numbers/hex/paths in the message are normalized away so the |
| 52 | + /// same logical message maps to the same code across occurrences. |
| 53 | + /// Format: <PREFIX>_<NODE>_<HASH>, clamped to medkit's [A-Z0-9_] / 64-char rule. |
| 54 | + std::string generate_fault_code(const std::string & node_name, const std::string & message) const; |
| 55 | + |
| 56 | + /// Normalize a log message into a stable template (lowercased, digit/hex/path |
| 57 | + /// runs stripped, whitespace collapsed). Exposed for unit testing. |
| 58 | + static std::string normalize_message(const std::string & message); |
| 59 | + |
| 60 | + /// Whether a given originating node should be promoted, honouring the |
| 61 | + /// include/exclude lists. Exposed for unit testing. |
| 62 | + bool node_is_eligible(const std::string & node_name) const; |
| 63 | + |
| 64 | + /// Map an rcl_interfaces/msg/Log.name (a logger name, e.g. "bt_navigator" or |
| 65 | + /// "controller_manager.resource_manager") to the originating node's |
| 66 | + /// fully-qualified name ("/bt_navigator", "/controller_manager"). The gateway |
| 67 | + /// discovers runtime entities by node FQN, so the fault's source_id must use |
| 68 | + /// the same form for faults (and their snapshots / rosbag) to associate with |
| 69 | + /// the entity in the SOVD tree. Exposed for unit testing. |
| 70 | + static std::string node_source_id(const std::string & log_name); |
| 71 | + |
| 72 | + private: |
| 73 | + void log_callback(const rcl_interfaces::msg::Log::ConstSharedPtr & msg); |
| 74 | + |
| 75 | + /// Fetch (or lazily create) the per-source FaultReporter for an originating |
| 76 | + /// node, so the fault's source_id is the node that logged, not the bridge. |
| 77 | + ros2_medkit_fault_reporter::FaultReporter * reporter_for(const std::string & node_name); |
| 78 | + |
| 79 | + void load_parameters(); |
| 80 | + |
| 81 | + static std::string to_upper_snake(const std::string & in, size_t max_len); |
| 82 | + |
| 83 | + rclcpp::Subscription<rcl_interfaces::msg::Log>::SharedPtr log_sub_; |
| 84 | + |
| 85 | + // One FaultReporter per originating node (correct source_id + own LocalFilter). |
| 86 | + std::map<std::string, std::unique_ptr<ros2_medkit_fault_reporter::FaultReporter>> reporters_; |
| 87 | + std::mutex reporters_mutex_; |
| 88 | + |
| 89 | + // Configuration |
| 90 | + std::string rosout_topic_; |
| 91 | + uint8_t severity_floor_; |
| 92 | + std::string code_prefix_; |
| 93 | + std::vector<std::string> exclude_nodes_; |
| 94 | + std::vector<std::string> include_only_nodes_; |
| 95 | + std::string own_node_name_; |
| 96 | +}; |
| 97 | + |
| 98 | +} // namespace ros2_medkit_log_bridge |
0 commit comments