完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
线上脚本出了 bug,最贵的不是修复成本,而是"不知道哪一行错的"。Lua 的 debug 库就是为这件事准备的:它能在运行时拿到调用栈、函数信息、甚至局部变量的值。996 引擎的 Lua 报错默认会打一行错误消息,但业务复杂之后,你需要自己把完整的栈带回来看。
最常用的组合是 xpcall + debug.traceback:普通 pcall 只返回"失败了"和一句错误字符串,而 xpcall 的第二个参数是错误处理器,在错误发生的瞬间被调用,此时 debug.traceback 能拿到完整调用链:
local function mainLogic()
local t = nil
return t.field -- 这里会炸
end
local ok, err = xpcall(mainLogic, function(e)
return e .. "\n" .. debug.traceback("stack:", 2)
end)
if not ok then
release_print("LUA ERROR:\n" .. err)
-- 这里可以把 err 上报到日志文件或 GM 频道
end
debug.getinfo 能查任意函数的元信息:定义在哪个文件、第几行、有几个 upvalue。排查"这个函数到底被谁改过"时特别有用——热更冲突经常表现为同名函数来源不明,debug.getinfo(f, "S").source 一打印就现形。
debug.getlocal / setlocal 在错误处理器里可以枚举出错那一帧的局部变量名和值。配合上面的 traceback,等于案发现场的物证清单:玩家 id 是多少、参数传成了什么,一目了然。
debug.sethook 可以设置调用钩子,在每行/每次函数调用时触发回调。它是性能剖析器和沙盒限制器的基础:统计每个函数被命中的次数就能找出热点函数;给钩子里加执行计数,超限就 error 掉,可以兜住第三方脚本的死循环。
把这套东西封装成全站统一的 SafeCall(函数, ...):内部 xpcall + traceback + 把错误串按"时间 + 玩家名 + 栈"写入日志文件,关键业务(充值回调、装备操作)全部走它。上线后每次玩家反馈异常,先翻日志再动手,定位速度完全不同。
两个注意点:debug 库本身有性能开销,traceback 只在错误路径上调用,不要放进高频循环;另外线上环境慎用 sethook 的"每行"模式,钩子触发太密集会拖慢整个 M2,做剖析时临时开、采完样立刻移除。
senddiymsg(play, jsonStr)
日志上报接口,错误栈还原后的第一件事是把结构化结果报上来
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| jsonStr | string | 日志json |
senddiymsg(play,jsonStr)
release_print(msg)
打印消息到控制台,开发期看完整栈,函数名与行号都在这里
| 参数 | 类型 | 说明 |
|---|---|---|
| msg | any | 打印内容 |
logact(play, logAct, loginfo, nParam1, nParam2, nParam3, nParam4, nParam5)
自定义日志,线上留一份关键字段,栈太长的场景按需截断
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| logAct | integer | 日志ID大于等于10000以上 |
| loginfo | string | 日志内容 |
| nParam1 | integer | 整数型(可空)最大支持21亿 |
| nParam2 | integer | 整数型(可空)最大支持21亿 |
| nParam3 | integer | 整数型(可空)最大支持21亿 |
printusetime(play, on, off)
打印脚本总耗时(微秒),错误栈采集本身有开销,别在高频路径上开
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| on | integer | 1=开始技术2=结束计时并打印耗时 |
| off | integer | 2=结束计时 |
handlerequest(actor, msgid, param1, param2, param3, sMsg)
监听消息,把消息处理包在保护调用里,才能拿到完整的调用上下文
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| msgid | integer | 消息ID(必填参数) |
| param1 | integer | 参数1(必填参数) |
| param2 | integer | 参数2(必填参数) |
| param3 | integer | 参数3(必填参数) |
| sMsg | string | 消息内容(必填参数) |