2、日志系统架构:Carla日志框架、日志级别与格式解析
大家好,我是你们的仿真日志分析讲师。今天我们来聊聊Carla仿真中一个特别基础、但又特别容易被忽视的东西——日志系统。
说实话,我见过太多同学一上来就闷头跑仿真,出了问题两眼一抹黑。日志就在那里,但没人去看。为什么?因为看不懂,或者觉得没必要。嗯,今天我就带你把这块硬骨头啃下来。
2.1 Carla日志框架:Python logging模块
Carla的日志系统,底层用的是Python标准库里的logging模块。你可能会问:为什么不用print?
我刚开始做仿真的时候,也喜欢用print。简单粗暴嘛。但后来发现,print出来的东西,你没法控制它的输出级别,也没法把它写到文件里。更麻烦的是,一旦仿真跑起来,print的输出会跟Carla引擎的输出混在一起,根本分不清谁是谁。
logging模块就不一样了。它提供了几个核心组件:
- Logger:日志记录器,你代码里调用的那个对象
- Handler:处理器,决定日志往哪写(控制台、文件、网络等)
- Formatter:格式化器,决定日志长什么样
- Filter:过滤器,决定哪些日志能通过
在Carla里,你打开Python API的源码,会看到类似这样的初始化代码:
import logging
# 创建一个logger
logger = logging.getLogger('carla')
logger.setLevel(logging.DEBUG)
# 创建一个控制台handler
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 创建一个文件handler
fh = logging.FileHandler('carla_simulation.log')
fh.setLevel(logging.INFO)
# 定义格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# 把handler加到logger上
logger.addHandler(ch)
logger.addHandler(fh)
你看,这样配置之后,DEBUG级别的日志会输出到控制台,INFO及以上的会写到文件里。各司其职,互不干扰。
2.2 日志级别:从DEBUG到CRITICAL
Carla沿用了Python logging的五个标准级别。我列个表,你一看就明白:
| 级别 | 数值 | 什么时候用 |
|---|---|---|
| DEBUG | 10 | 调试信息,比如传感器数据、坐标变换、每帧状态 |
| INFO | 20 | 正常流程信息,比如「车辆已生成」「仿真开始」 |
| WARNING | 30 | 可能有问题但不影响运行,比如「传感器未找到,使用默认配置」 |
| ERROR | 40 | 出错了但还能继续,比如「交通灯数据解析失败,跳过该帧」 |
| CRITICAL | 50 | 严重错误,仿真无法继续,比如「Carla服务器连接丢失」 |
这里有个关键点:日志级别是有继承关系的。你设置logger.setLevel(logging.WARNING),那么WARNING、ERROR、CRITICAL都会输出,但DEBUG和INFO会被过滤掉。
为什么会这样?说白了,就是怕你被海量信息淹死。你想想看,一个仿真跑起来,每帧都有几十上百条DEBUG日志,要是全打印到控制台,你根本找不到重点。
2.3 日志格式解析:看懂每一行
好,现在日志有了,但你看得懂吗?我们来看一个典型的Carla日志行:
2025-01-15 14:23:45,123 - carla.vehicle - WARNING - Vehicle 42 is out of the map, teleporting to spawn point
这一行里包含了几个关键信息:
- 时间戳:
2025-01-15 14:23:45,123,精确到毫秒。这个在排查时序问题时特别有用。 - 日志器名称:
carla.vehicle,告诉你这条日志来自哪个模块。Carla里常见的名称有carla.client、carla.sensor、carla.traffic等。 - 日志级别:
WARNING,告诉你这条日志的严重程度。 - 消息内容:
Vehicle 42 is out of the map...,具体发生了什么。
你可能会问:这个格式是怎么定义的?其实就是前面代码里的那一行:
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
常见的格式占位符还有这些:
| 占位符 | 含义 | 示例 |
|---|---|---|
| %(asctime)s | 时间戳 | 2025-01-15 14:23:45,123 |
| %(name)s | 日志器名称 | carla.vehicle |
| %(levelname)s | 日志级别 | WARNING |
| %(message)s | 日志内容 | Vehicle 42 is out of the map |
| %(filename)s | 文件名 | vehicle_manager.py |
| %(lineno)d | 行号 | 128 |
| %(funcName)s | 函数名 | update_vehicle_state |
我个人习惯在调试阶段加上%(filename)s和%(lineno)d,这样一旦报错,我能立刻定位到是哪一行代码出的问题。但正式跑仿真时我会去掉,因为文件路径和行号会让日志变得很长,影响阅读。
2.4 实战小技巧:自定义你的日志格式
最后,我分享一个我自己常用的日志配置模板。你可以直接拿去用:
import logging
import sys
def setup_carla_logger(name='carla', level=logging.INFO):
logger = logging.getLogger(name)
logger.setLevel(level)
# 控制台handler:只显示INFO及以上
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_format = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(message)s',
datefmt='%H:%M:%S'
)
console_handler.setFormatter(console_format)
# 文件handler:记录所有级别,包含详细信息
file_handler = logging.FileHandler('carla_debug.log')
file_handler.setLevel(logging.DEBUG)
file_format = logging.Formatter(
'%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s'
)
file_handler.setFormatter(file_format)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger
# 使用
log = setup_carla_logger()
log.info('仿真开始')
log.debug('传感器数据: x=1.23, y=4.56')
你看,控制台输出简洁明了,文件里则保留了完整的调试信息。这样既不会在控制台被刷屏,又能在需要时翻文件查细节。
嗯,日志系统这块,今天就讲到这里。下一节我们会聊怎么通过日志来定位仿真中的常见故障。到时候我会拿几个我实际踩过的坑来举例,保证让你印象深刻。