0Pricing
Lua Academy · 课时

xpcall 和消息处理器

将 xpcall 与自定义处理器结合使用,以获取详细的错误回溯信息。

xpcall 和消息处理器 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。

xpcall 语法

xpcall(f, handler, ...) 会在受保护模式下使用参数 ... 调用 f,发生错误时调用 handler(errorObject)。处理函数的返回值会成为 xpcall 的第二个返回值。与 pcall 不同,xpcall 会在调用栈仍然完整时运行处理函数。

local function handler(err)
  return "HANDLED: " .. tostring(err)
end

local ok, msg = xpcall(
  function() error("something bad") end,
  handler
)

print(ok)   -- false
print(msg)  -- HANDLED: ...: something bad

将 debug.traceback 用作处理函数

最常见的 xpcall 处理函数是 debug.traceback。请直接将它作为处理函数传入——它会为错误附加完整的调用栈跟踪,这对于调试生产环境中的错误非常宝贵。

local function level3() error("deep error") end
local function level2() level3() end
local function level1() level2() end

local ok, err = xpcall(level1, debug.traceback)

if not ok then
  -- err contains the full stack trace
  print(err)
end

带上下文的自定义处理函数

自定义处理函数可以添加上下文:添加时间戳、记录到文件、通知监控系统,然后返回格式化后的错误。这是 Lua 应用进行生产环境错误处理的标准模式。

local function errorHandler(err)
  local trace = debug.traceback(err, 2)
  local ts = os.date("%H:%M:%S")
  local report = string.format("[%s] ERROR\n%s", ts, trace)
  -- Could log to file here
  io.stderr:write(report .. "\n")
  return report
end

local ok, msg = xpcall(
  function()
    local t = nil
    return t.field   -- error!
  end,
  errorHandler
)
print("ok:", ok)

处理函数不能发生错误

如果消息处理函数本身抛出错误,Lua 会返回错误指示,而不会再次调用该处理函数。请始终编写足够可靠的处理函数——不要执行可能失败的 IO 操作,也不要对 nil 进行索引。

local function safeHandler(err)
  -- Keep handler simple and safe
  local ok, trace = pcall(debug.traceback, err, 2)
  if ok then return trace
  else return tostring(err) .. " (traceback failed)"
  end
end

local ok, msg = xpcall(
  function() error({complex="error table"}) end,
  safeHandler
)
print(ok, type(msg))

在主循环中使用 xpcall

在长时间运行的程序(服务器、游戏循环)中,请将主函数包装在 xpcall 中,以捕获并记录所有未处理的错误而不使程序崩溃。随后主循环可以决定是重启还是退出。

local function mainApp()
  -- simulate work
  for i = 1, 3 do
    print("Tick", i)
    if i == 2 then error("transient error") end
  end
end

local function handler(e)
  return debug.traceback("App error: "..tostring(e), 2)
end

local ok, err = xpcall(mainApp, handler)
if not ok then
  print("Application crashed:\n" .. err)
end

结构化错误报告

将 xpcall 与结构化错误对象及功能丰富的处理函数结合起来,可以生成详细且可操作的错误报告,用于调试或监控面板。

local function handler(err)
  local info = {
    error   = tostring(err),
    time    = os.date("!%Y-%m-%dT%H:%M:%SZ"),
    trace   = debug.traceback(nil, 2),
  }
  return info
end

local ok, report = xpcall(
  function() error({code=500, msg="internal error"}) end,
  handler
)

if not ok then
  print("Time:", report.time)
  print("Error:", report.error)
  -- print("Trace:", report.trace)
end

在协程中使用 xpcall

在协程内部,pcall 可以正常工作。若要获取协程内部错误的调用栈跟踪,请将协程主体包装在 xpcall 中。处理函数会在协程的调用栈上下文中运行。

local function co_body()
  error("error inside coroutine")
end

local co = coroutine.create(function()
  local ok, err = xpcall(co_body, debug.traceback)
  if not ok then
    print("Caught in coroutine:", err:match("([^\n]+)"))
  end
end)

coroutine.resume(co)

丰富错误对象

处理函数可以将普通字符串错误丰富为完整对象,也可以进一步丰富已有的完整对象。这样,底层代码可以抛出简单错误,而处理函数负责添加上下文(请求 ID、用户会话、环境信息)。

local requestID = "req-123"

local function handler(err)
  if type(err) == "string" then
    return {message=err, requestID=requestID, level="error"}
  end
  err.requestID = requestID
  return err
end

local ok, result = xpcall(
  function() error("database timeout") end,
  handler
)

if not ok then
  print(result.message, result.requestID)
  -- database timeout  req-123
end

比较 pcall 和 xpcall

以下情况使用 pcall:您只需要错误值、错误是预期情况并会在当前代码中处理、或者您重视简洁性。以下情况使用 xpcall:您需要调用栈跟踪、位于顶层边界、或希望为所有错误添加上下文。

-- pcall: simple, no overhead
local ok, err = pcall(function()
  return 1/0   -- no error in Lua! returns inf
end)
print(ok, err)   -- true  inf

-- xpcall: adds traceback
local ok2, err2 = xpcall(
  function() error("real error") end,
  debug.traceback
)
print(ok2)       -- false
print(err2:sub(1,40))  -- first line of traceback

处理函数的返回值

处理函数返回的任何内容都会成为 xpcall 返回的第二个值。如果处理函数返回 nil,xpcall 的第二个返回值就是 nil。让处理函数返回原始错误对象以及额外信息,是最灵活的做法。

local function enrichedHandler(err)
  return {
    original = err,
    traceback = debug.traceback(nil, 2),
    timestamp = os.time(),
  }
end

local ok, report = xpcall(
  function() error("oops") end,
  enrichedHandler
)

if not ok then
  print(type(report))          -- table
  print(report.original)       -- ...: oops
  print(report.timestamp > 0)  -- true
end

快速检查

xpcall 相比 pcall 的主要优势是什么?

回顾:xpcall

总结:

  • xpcall(f, handler, ...) — 处理函数会在调用栈仍然完整时运行
  • 使用 debug.traceback 作为处理函数,以获取完整的调用栈跟踪
  • 处理函数不得发生错误——请保持其简单可靠
  • 在处理函数中为错误添加上下文
  • 将主循环/服务器处理函数包装在 xpcall 中
  • 处理函数的返回值会成为 xpcall 的第二个返回值

常见问题解答

「xpcall 和消息处理器」课时是免费的吗?

是的 — 「xpcall 和消息处理器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。

「xpcall 和消息处理器」这节课中我会学到什么?

将 xpcall 与自定义处理器结合使用,以获取详细的错误回溯信息。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Lua Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「xpcall 和消息处理器」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Lua Academy 课中编写并运行代码吗?

能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. error() 函数
  2. 使用 pcall 进行受保护调用
  3. xpcall 和消息处理器
  4. 结构化错误对象
← 返回 Lua Academy