Ruby 日志实战:标准库 Logger 从入门到生产配置

Ruby 标准库 Logger 中文实战:Logger.new 与六个日志级别、formatter 自定义格式、progname 与 datetime_format、shift_age/shift_size 日志轮转、多输出与 JSON 结构化技巧,以及观测云 DataKit 采集落地方案。

最佳实践
Ruby 日志实战:标准库 Logger 从入门到生产配置技术指南封面

Ruby 标准库内置的 Logger 类覆盖了日志的基础需求:级别、格式化、轮转一应俱全,通常可 require 使用,但应按 Ruby 发行版和 Bundler 环境确认 logger gem 可用,必要时显式声明依赖。本文系统讲解 Logger 的核心用法、生产化配置技巧(JSON 输出、轮转、多目的地),以及如何把 Ruby 应用日志接入观测云。

核心要点速览

  • Logger 是 Ruby 标准库:require "logger" 即用,提供 DEBUG/INFO/WARN/ERROR/FATAL/UNKNOWN 六个级别。
  • 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 < UNKNOWN。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?(Exception) ? msg.message : (msg.is_a?(String) ? msg : msg.inspect),
  }
  if msg.is_a?(Exception)
    payload[:exception] = {class: msg.class.name, backtrace: msg.backtrace}
  end
  "#{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 内建轮转,两种模式:

require "fileutils"
FileUtils.mkdir_p("logs")
# 按时间:daily/weekly/monthly
Logger.new("logs/app.log", "daily")

# 按大小:shift_age=7 通常包含当前文件(最多6个归档),阈值100MiB
Logger.new("logs/app.log", 7, 100 * 1024 * 1024)

第二种签名 Logger.new(path, shift_age, shift_size):文件超过 shift_size 后轮转,标准实现的正整数 shift_age 通常计入当前文件;按 daily 等时间轮转并不自动保证总归档数量有界,仍需保留策略。

记录异常

begin
  process_payment(order)
rescue => e
  logger.error("扣款失败 order_id=#{order.id} error=#{e.class}: #{e.message}\n#{Array(e.backtrace).first(10).join("\n")}")
end

默认 formatter 收到 Exception 对象时会包含异常和堆栈;传字符串或使用自定义 formatter 时需自行保留这些信息。formatter 里也可以判断 msg.is_a?(Exception) 统一处理堆栈输出。

多目的地输出

标准 Logger 只支持单一目的地。要多路输出,常见做法是包一层:

class MultiLogger
  def initialize(*loggers)
    @loggers = loggers
  end
  Logger::Severity.constants.each do |level|
    define_method(level.downcase) { |*args, &blk| @loggers.each { |l| l.send(level.downcase, *args, &blk) } }
  end
end

此简单转发器会为每个启用相应级别的 logger 再次求值消息块;不要在块中放有副作用的操作。某一路抛出异常也可能中断后续转发。

更省事的选择是换支持多 appender 的库(如 Semantic Logger,见本系列专篇)。容器化部署时最推荐的做法是只写 stdout——多目的地分发交给采集层。

检查集中采集后的字段

应用使用上述 JSON formatter 后,主机部署可通过观测云 DataKit 日志采集读取轮转文件,容器部署则先核对运行时日志路径及权限。按实际输出配置字段提取,把 level 映射为日志级别、把 timestamp 解析为事件时间,并保留业务请求标识。

发送一条带异常堆栈的测试日志,在查看器检查消息是否完整、时间是否正确。确认后,按服务和错误级别建立日志检测,设置检测窗口及通知对象;不要把解析失败的日志当成应用没有报错。

常见问题(FAQ)

Ruby 项目必须用标准库 Logger 吗?

不是必须,但它是零依赖的稳妥起点。需要多 appender、异步写入、payload 结构化字段时,Semantic Logger 是 Ruby 生态的主流选择;Rails 项目还可以用 Lograge 优化请求日志。

Logger 是线程安全的吗?

标准 Logger 对自身写入有同步保护,但不保证其他 logger、进程或直接写同一目标的代码不会交错,自定义 formatter 的共享状态也需线程安全。

日志时间格式怎么统一?

在 formatter 中用 datetime.strftime 或 datetime.iso8601(3)(需先 require "time")输出 ISO-8601 带毫秒格式,生产环境建议统一时区(如东八区或 UTC),避免多机日志对不上时间。

老代码里散落着 puts 怎么办?

puts 没有内建级别和时间戳,但 stdout 被运行时或重定向收集时也可进入日志文件,生产环境应全部替换为 logger 调用。可以短期用一个 $stdout 重定向的临时方案兜底采集,但长期必须改代码。

系列阅读


[1]: 观测云文档《DataKit 日志采集综述》https://docs.guance.com/integrations/datakit-logging/
[2]: 观测云文档《日志查看器》https://docs.guance.com/logs/explorer/
[3]: 观测云文档《监控》https://docs.guance.com/monitoring/
[4]: 观测云文档《日志-索引》https://docs.guance.com/logs/multi-index/

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台