You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 21:24:56