1、日志记录器
Logger 是什么?
-
一个可以运行时动态添加到行为树的观察者类
-
采用观察者模式的非侵入式实现
-
在每个节点状态改变时自动触发回调
回调函数参数
cpp
void callback(
BT::Duration timestamp, // 状态变化发生的时间点
const TreeNode& node, // 状态变化的节点引用
NodeStatus prev_status, // 变化前的状态
NodeStatus status); // 变化后的状态
使用示例
cpp
#include "behaviortree_cpp/bt_factory.h"
#include "behaviortree_cpp/loggers/bt_cout_logger.h"
// 自定义 Logger 类
class MyCustomLogger : public BT::StatusChangeLogger
{
public:
MyCustomLogger(const BT::Tree& tree) : StatusChangeLogger(tree) {}
virtual void callback(
BT::Duration timestamp,
const BT::TreeNode& node,
BT::NodeStatus prev_status,
BT::NodeStatus status) override
{
// 记录时间戳(毫秒)
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp);
// 输出节点状态变化信息
std::cout << "[" << ms.count() << "ms] "
<< node.name() << ": "
<< BT::toStr(prev_status)
<< " -> "
<< BT::toStr(status)
<< std::endl;
// 可以添加更多自定义逻辑
if(status == BT::NodeStatus::FAILURE) {
std::cout << " ⚠️ 节点 " << node.name() << " 执行失败!" << std::endl;
}
}
virtual void flush() override {
std::cout << "--- Logger 数据刷新 ---" << std::endl;
}
};
int main()
{
BT::BehaviorTreeFactory factory;
// 注册节点(示例)
factory.registerSimpleAction("SayHello", []{
std::cout << "Hello!" << std::endl;
return BT::NodeStatus::SUCCESS;
});
// 创建行为树
auto tree = factory.createTreeFromText(`
<root>
<Sequence>
<SayHello />
<Wait duration="1000"/>
</Sequence>
</root>
`);
// 1. 使用内置的 StdCoutLogger(输出到控制台)
BT::StdCoutLogger logger_cout(tree);
// 2. 使用内置的 FileLogger(输出到文件)
BT::FileLogger logger_file(tree, "bt_trace.fbl");
// 3. 使用自定义 Logger
MyCustomLogger logger_custom(tree);
// 4. 使用多个 Logger(可同时添加多个)
std::vector<std::unique_ptr<BT::StatusChangeLogger>> loggers;
loggers.emplace_back(new BT::StdCoutLogger(tree));
loggers.emplace_back(new BT::FileLogger(tree, "trace.fbl"));
// 执行行为树
tree.tickWhileRunning();
return 0;
}
添加时机 :在行为树创建后、开始执行前添加 Logger
内置 Logger 类型
BT.CPP 提供的内置 Logger:
-
BT::StdCoutLogger:输出到标准控制台 -
BT::FileLogger:输出到二进制文件(可使用工具分析) -
BT::MinitraceLogger:生成 Chrome Tracing 格式 -
BT::PublisherZMQ:通过 ZeroMQ 发布状态变化
这种设计使得行为树的调试、监控和性能分析变得非常灵活,无需修改节点实现即可获得完整的执行跟踪。
2、TreeObserver 设计分析
目的
TreeObserver 是一个行为树节点的观察者/日志记录器,主要用于:
-
单元测试:验证特定条件下节点是否按预期执行
-
性能监控:统计节点执行频率和结果分布
-
调试:追踪节点状态变化历史
-
行为分析:了解树的实际执行路径
NodeStatistics 结构体解析
cpp
struct NodeStatistics {
NodeStatus last_result; // 最后一次有效结果(仅 SUCCESS/FAILURE)
NodeStatus current_status; // 当前状态(包括 IDLE/SKIPPED)
unsigned transitions_count; // 状态转换次数(不含 IDLE)
unsigned success_count; // 转换为 SUCCESS 的次数
unsigned failure_count; // 转换为 FAILURE 的次数
unsigned skip_count; // 转换为 SKIPPED 的次数
Duration last_timestamp; // 最后一次状态转换的时间戳
};
代码示例:

xml
cpp
<root BTCPP_format="4">
<BehaviorTree ID="MainTree">
<Sequence>
<Fallback>
<AlwaysFailure name="failing_action"/>
<SubTree ID="SubTreeA" name="mysub"/>
</Fallback>
<AlwaysSuccess name="last_action"/>
</Sequence>
</BehaviorTree>
<BehaviorTree ID="SubTreeA">
<Sequence>
<AlwaysSuccess name="action_subA"/>
<SubTree ID="SubTreeB" name="sub_nested"/>
<SubTree ID="SubTreeB" />
</Sequence>
</BehaviorTree>
<BehaviorTree ID="SubTreeB">
<AlwaysSuccess name="action_subB"/>
</BehaviorTree>
</root>
c++
cpp
#include "behaviortreedeps/include/behaviortree_cpp/bt_factory.h"
#include "behaviortreedeps/include/behaviortree_cpp/loggers/bt_observer.h"
using namespace BT;
namespace chr = std::chrono;
int main()
{
BT::BehaviorTreeFactory factory;
factory.registerBehaviorTreeFromFile("./tree.xml");
auto tree = factory.createTree("MainTree");
tree.tickWhileRunning();
// Helper function to print the tree.
BT::printTreeRecursively(tree.rootNode());
// The purpose of the observer is to save some statistics about the number of times
// a certain node returns SUCCESS or FAILURE.
// This is particularly useful to create unit tests and to check if
// a certain set of transitions happened as expected
BT::TreeObserver observer(tree);
// Print the unique ID and the corresponding human readable path
// Path is also expected to be unique.
std::map<uint16_t, std::string> ordered_UID_to_path;
for (const auto& [name, uid] : observer.pathToUID())
{
ordered_UID_to_path[uid] = name;
}
for (const auto& [uid, name] : ordered_UID_to_path)
{
std::cout << uid << " -> " << name << std::endl;
}
tree.tickWhileRunning();
// You can access a specific statistic, using is full path or the UID
const auto& last_action_stats = observer.getStatistics("last_action");
assert(last_action_stats.transitions_count > 0);
std::cout << "----------------" << std::endl;
// print all the statistics
for (const auto& [uid, name] : ordered_UID_to_path)
{
const auto& stats = observer.getStatistics(uid);
std::cout << "[" << name << "] \tT/S/F: " << stats.transitions_count << "/" << stats.success_count << "/" << stats.failure_count << std::endl;
}
return 0;
}
执行结果 :

总结:
Logger和TreeObserver都是树行为调试的工具 但是感觉可视化效果不太好