1414
1515#pragma once
1616
17- #include < map >
17+ #include < list >
1818#include < memory>
1919#include < mutex>
2020#include < string>
21+ #include < unordered_map>
2122#include < vector>
2223
2324#include " rclcpp/rclcpp.hpp"
@@ -30,11 +31,11 @@ namespace ros2_medkit_log_bridge {
3031// /
3132// / Subscribes to /rosout (rcl_interfaces/msg/Log) and forwards entries at or
3233// / 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+ // / fault to the originating node's fully-qualified name via a per-source
3435// / FaultReporter. Drop-in compat adapter, same category as
3536// / ros2_medkit_diagnostic_bridge: native FaultReporter instrumentation stays
3637// / 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+ // / Level mapping and the WARN/LocalFilter caveat are documented in README.md.
3839// /
3940// / Hard limitation by construction: only sees rclcpp logs that reach /rosout
4041// / from a still-alive node. Console-only loggers and crash-before-flush are out
@@ -47,19 +48,20 @@ class LogBridgeNode : public rclcpp::Node {
4748 // / Returns false when the level is below the floor / not promotable.
4849 static bool map_level_to_severity (uint8_t log_level, uint8_t severity_floor, uint8_t * severity_out);
4950
50- // / Auto-generate a stable fault code from the originating node name and the
51+ // / Auto-generate a stable fault code from the originating node's FQN and the
5152 // / log message. Numbers/hex/paths in the message are normalized away so the
5253 // / same logical message maps to the same code across occurrences.
5354 // / 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+ std::string generate_fault_code (const std::string & source_id , const std::string & message) const ;
5556
5657 // / Normalize a log message into a stable template (lowercased, digit/hex/path
57- // / runs stripped, whitespace collapsed). Exposed for unit testing.
58+ // / runs stripped, whitespace collapsed, isolated single-letter tokens dropped).
59+ // / Exposed for unit testing.
5860 static std::string normalize_message (const std::string & message);
5961
6062 // / Whether a given originating node should be promoted, honouring the
6163 // / include/exclude lists. Exposed for unit testing.
62- bool node_is_eligible (const std::string & node_name ) const ;
64+ bool node_is_eligible (const std::string & source_id ) const ;
6365
6466 // / Map an rcl_interfaces/msg/Log.name (a logger name, e.g. "bt_navigator" or
6567 // / "controller_manager.resource_manager") to the originating node's
@@ -69,29 +71,54 @@ class LogBridgeNode : public rclcpp::Node {
6971 // / the entity in the SOVD tree. Exposed for unit testing.
7072 static std::string node_source_id (const std::string & log_name);
7173
72- private:
73- void log_callback (const rcl_interfaces::msg::Log::ConstSharedPtr & msg);
74+ // / FNV-1a 32-bit hash, fixed spec, emitted as 8 lowercase hex chars. Exposed
75+ // / for unit testing so a known input asserts a known constant.
76+ static std::string fnv1a_hex (const std::string & in);
77+
78+ // / Whether a fault_code may be forwarded now under the per-code cooldown
79+ // / (first occurrence passes; same code within report_cooldown_sec is
80+ // / suppressed; 0.0 disables). Exposed for unit testing.
81+ bool cooldown_allows (const std::string & fault_code, rclcpp::Time now);
7482
7583 // / Fetch (or lazily create) the per-source FaultReporter for an originating
7684 // / 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);
85+ // / Bounded by max_tracked_nodes_ with LRU eviction. Exposed for unit testing.
86+ ros2_medkit_fault_reporter::FaultReporter * reporter_for (const std::string & source_id);
87+
88+ // / Number of currently tracked per-node reporters. Exposed for unit testing.
89+ size_t tracked_reporter_count ();
90+
91+ private:
92+ void log_callback (const rcl_interfaces::msg::Log::ConstSharedPtr & msg);
7893
7994 void load_parameters ();
8095
8196 static std::string to_upper_snake (const std::string & in, size_t max_len);
8297
8398 rclcpp::Subscription<rcl_interfaces::msg::Log>::SharedPtr log_sub_;
8499
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_;
100+ // One FaultReporter per originating node (correct source_id + own LocalFilter),
101+ // LRU-ordered: front() is the least-recently-used entry.
102+ struct ReporterEntry {
103+ std::string source_id;
104+ std::unique_ptr<ros2_medkit_fault_reporter::FaultReporter> reporter;
105+ };
106+ std::list<ReporterEntry> reporters_lru_;
107+ std::unordered_map<std::string, std::list<ReporterEntry>::iterator> reporters_;
87108 std::mutex reporters_mutex_;
88109
110+ // Per-fault_code forward cooldown: last time a code was forwarded.
111+ std::unordered_map<std::string, rclcpp::Time> last_forward_;
112+ std::mutex cooldown_mutex_;
113+
89114 // Configuration
90115 std::string rosout_topic_;
91116 uint8_t severity_floor_;
92117 std::string code_prefix_;
93118 std::vector<std::string> exclude_nodes_;
94119 std::vector<std::string> include_only_nodes_;
120+ int max_tracked_nodes_;
121+ double report_cooldown_sec_;
95122 std::string own_node_name_;
96123};
97124
0 commit comments