Ruby 日志实战:标准库 Logger 从入门到生产配置
Ruby 标准库 Logger 中文实战:Logger.new 与六个日志级别、formatter 自定义格式、progname 与 datetime_format、shift_age/shift_size 日志轮转、多输出与 JSON 结构化技巧,以及观测云 DataKit 采集落地方案。
Ruby 标准库内置的 Logger 类覆盖了日志的基础需求:级别、格式化、轮转一应俱全,无需任何 gem 即可使用。本文系统讲解 Logger 的核心用法、生产化配置技巧(JSON 输出、轮转、多目的地),以及如何把 Ruby 应用日志接入观测云。
核心要点速览
- Logger 是 Ruby 标准库:
require "logger"即用,提供 DEBUG/INFO/WARN/ERROR/FATAL/公开资料未说明 六个级别。 - formatter 是定制输出的关键:默认格式简陋,生产环境应自定义为带时间戳的键值对或 JSON。
- 轮转内建:
Logger.new("app.log", "daily")或shift_age/shift_size参数防止磁盘写满。 - 集中采集:先固定 JSON 输出字段,再检查时间、级别和异常堆栈的解析结果。
快速上手:创建 logger 与日志级别
require "logger"
logger = Logger.new($stdout)
logger.level = Logger::INFO
logger.debug("调试细节") # 不输出(级别为 INFO)
logger.info("服务启动 port=3000")
logger.warn("配置项缺失,使用默认值")
logger.error("数据库连接失败")
logger.fatal("无法恢复,进程退出")
输出(默认格式):
I, [2026-08-25T14:40:11.552301 #1294858] INFO -- : 服务启动 port=3000
W, [2026-08-25T14:40:11.553120 #1294858] WARN -- : 配置项缺失,使用默认值
六个级别按严重程度递增:DEBUG < INFO < WARN < ERROR < FATAL < 公开资料未说明。level 控制最低输出级别;也可以用 logger.info? 判断当前级别是否启用。
块写法在级别不够时不执行,可避免无谓的字符串拼接:
logger.debug { "昂贵的调试信息 #{expensive_compute}" }
默认格式不够用?自定义 formatter
Logger 的 formatter 是一个接收 (severity, datetime, progname, msg) 的 proc:
logger.formatter = proc do |severity, datetime, progname, msg|
"[#{datetime.strftime('%Y-%m-%dT%H:%M:%S%z')}] #{severity} #{msg}\n"
end
输出:[2026-08-25T14:40:11+0800] INFO 服务启动 port=3000
progname 可用于标记子系统:logger = Logger.new($stdout); logger.progname = "OrderService"。
生产化:JSON 结构化输出
纯文本日志难以被平台解析,生产建议直接输出 JSON:
require "logger"
require "json"
logger = Logger.new($stdout)
logger.formatter = proc do |severity, datetime, progname, msg|
payload = {
timestamp: datetime.strftime("%Y-%m-%dT%H:%M:%S.%L%z"),
level: severity,
progname: progname,
message: msg.is_a?(String) ? msg : msg.inspect,
}
"#{JSON.generate(payload)}\n"
end
logger.info("用户登录")
{"timestamp":"2026-08-25T14:41:05.123+0800","level":"INFO","progname":null,"message":"用户登录"}
JSON 输出后,集中采集端仍需按输出结构配置字段提取,并核对解析结果。如果需要带字段的结构化记录(user_id=123),可以在 msg 里传 Hash 并在 formatter 中合并进 payload。
日志轮转:别让磁盘写满
Logger 内建轮转,两种模式:
# 按时间:daily/weekly/monthly
Logger.new("logs/app.log", "daily")
# 按大小:保留 7 个 100MB 的历史文件
Logger.new("logs/app.log", 7, 100 * 1024 * 1024)
第二种签名 Logger.new(path, shift_age, shift_size):文件超过 shift_size 后轮转,最多保留 shift_age 个历史文件。
记录异常
begin
process_payment(order)
rescue => e
logger.error("扣款失败 order_id=#{order.id} error=#{e.class}: #{e.message}\n#{e.backtrace.first(10).join("\n")}")
end
Ruby 的 Logger 不会自动附加堆栈——必须显式记录 e.backtrace,否则错误日志只剩一行消息,排查时缺少最关键的上下文。formatter 里也可以判断 msg.is_a?(Exception) 统一处理堆栈输出。
多目的地输出
标准 Logger 只支持单一目的地。要多路输出,常见做法是包一层:
class MultiLogger
def initialize(*loggers) = @loggers = loggers
Logger::Severity.constants.each do |level|
define_method(level.downcase) { |*args, &blk| @loggers.each { |l| l.send(level.downcase, *args, &blk) } }
end
end
更省事的选择是换支持多 appender 的库(如 Semantic Logger,见本系列专篇)。容器化部署时最推荐的做法是只写 stdout——多目的地分发交给采集层。
检查集中采集后的字段
应用使用上述 JSON formatter 后,主机部署可通过观测云 DataKit 日志采集读取轮转文件,容器部署则先核对运行时日志路径及权限。按实际输出配置字段提取,把 level 映射为日志级别、把 timestamp 解析为事件时间,并保留业务请求标识。
发送一条带异常堆栈的测试日志,在查看器检查消息是否完整、时间是否正确。确认后,按服务和错误级别建立日志检测,设置检测窗口及通知对象;不要把解析失败的日志当成应用没有报错。
常见问题(FAQ)
Ruby 项目必须用标准库 Logger 吗?
不是必须,但它是零依赖的稳妥起点。需要多 appender、异步写入、payload 结构化字段时,Semantic Logger 是 Ruby 生态的主流选择;Rails 项目还可以用 Lograge 优化请求日志。
Logger 是线程安全的吗?
是。标准库 Logger 内部有锁保护,多线程共享一个 logger 实例是安全的,不会写交错。
日志时间格式怎么统一?
在 formatter 中用 datetime.strftime 或 datetime.iso8601(3) 输出 ISO-8601 带毫秒格式,生产环境建议统一时区(如东八区或 UTC),避免多机日志对不上时间。
老代码里散落着 puts 怎么办?
puts 没有级别、没有时间戳、不进日志文件,生产环境应全部替换为 logger 调用。可以短期用一个 $stdout 重定向的临时方案兜底采集,但长期必须改代码。
系列阅读
- 上一篇:Go 日志库九款对比
- 下一篇:Rails 日志实战
- 相关阅读:Semantic Logger 实战 | 日志格式化最佳实践