使用Cython编译ROS2 Python节点后无法正常调用日志功能
ROS2 Cython编译后日志报错:Logger severity cannot be changed between calls.
问题根源
你的问题出在Cython编译后,Python的inspect模块无法正确获取调用栈的唯一标识信息。rcutils_logger中的CallerId类依赖frame.f_lasti(字节码指令指针)来区分同一行的不同调用,但Cython编译后,多次日志调用的frame信息会被优化复用,导致两次info和warn调用生成的CallerId完全相同。ROS2的日志机制默认禁止同一个调用点连续切换日志级别,因此触发了该错误。
解决方案
1. 手动构造CallerId并传入日志调用
绕过自动获取CallerId的逻辑,手动为每个日志调用生成唯一的CallerId,强制区分不同的日志语句:
from rclpy.impl.rcutils_logger import CallerId import inspect import os def __init__(self): logger = self.get_logger() # 为INFO日志构造唯一CallerId frame_info = inspect.getframeinfo(inspect.currentframe()) caller_id_info = CallerId._make([ frame_info.function, os.path.abspath(frame_info.filename), frame_info.lineno, # 手动指定不同的last_index值,避免重复 1001 ]) logger.log(logger.get_severity_level('INFO'), "Starting my node!", caller_id=caller_id_info) # 为WARN日志构造另一个唯一CallerId caller_id_warn = CallerId._make([ frame_info.function, os.path.abspath(frame_info.filename), frame_info.lineno + 1, # 用行号区分,或修改last_index为1002 1002 ]) logger.log(logger.get_severity_level('WARN'), "My node has already started!", caller_id=caller_id_warn)
2. 修改rcutils_logger的CallerId生成逻辑
如果可以修改rclpy的源码,给CallerId添加一个全局计数器,在Cython环境下用计数器区分不同调用:
import itertools from collections import namedtuple import inspect import os class CallerId( namedtuple('CallerId', ['function_name', 'file_path', 'line_number', 'last_index', 'cython_counter'])): # 全局计数器,用于Cython环境下区分调用 _cython_counter = itertools.count() def __new__(cls, frame=None): if not frame: frame = _find_caller(inspect.currentframe()) # 判断是否为Cython编译的代码(文件名是.so或<string>) is_cython = frame.f_code.co_filename.endswith('.so') or frame.f_code.co_filename == '<string>' cython_counter = next(cls._cython_counter) if is_cython else 0 return super(CallerId, cls).__new__( cls, function_name=frame.f_code.co_name, file_path=os.path.abspath(inspect.getframeinfo(frame).filename), line_number=frame.f_lineno, last_index=frame.f_lasti, cython_counter=cython_counter, )
同时要确保rcutils_logger中比较CallerId的逻辑包含cython_counter字段,这样即使其他字段重复,计数器不同也会被判定为不同调用点。
3. 临时禁用日志级别切换检查
如果不需要该保护机制,可以直接注释掉rcutils_logger中的报错逻辑:
找到rcutils_logger.py中抛出错误的代码段:
if self._last_caller_id == caller_id and self._last_severity != severity: raise RuntimeError("Logger severity cannot be changed between calls.")
修改为:
# 注释掉检查,适配Cython环境 # if self._last_caller_id == caller_id and self._last_severity != severity: # raise RuntimeError("Logger severity cannot be changed between calls.")
注意:此方法会失去ROS2对同一调用点频繁切换日志级别的限制,仅建议临时应急使用。
4. 调整Cython编译选项保留调试信息
在setup.py中添加编译参数,让Cython保留行号和调用栈信息,使inspect模块能正常工作:
from setuptools import setup from Cython.Build import cythonize setup( ext_modules=cythonize( "your_node.pyx", compiler_directives={ 'language_level': 3, 'linetrace': True, # 启用行追踪 'binding': True }, # 编译时添加宏定义 compile_time_env={'CYTHON_TRACE': '1'} ) )
此方法会降低编译后代码的性能,适合需要保留原有日志调用方式且对性能要求不极端的场景。
内容的提问来源于stack exchange,提问作者complikator
相关产品推荐
相关产品推荐

