找回密码
 register
搜索
查看: 11|回复: 0

[文档笔记] 完善 TLBB 服务端 Lua 报错日志:让 `attempt to call a nil value` 定位到脚本、函数和行号

[复制链接]
  • 打卡等级:本地老炮
  • 打卡总天数:584
  • 打卡月天数:20
  • 打卡总奖励:581
  • 最近打卡:2026-08-29 14:32:15
Waylee 发表于 2026-8-29 17:07 | 显示全部楼层 |阅读模式 | Google Chrome | Windows 10

马上注册,查看网站隐藏内容!!

您需要 登录 才可以下载或查看,没有账号?register

×

TLBB 服务端执行 Lua 脚本出错时,luaerror.log 原本只能看到:

attempt to call a nil value
ScriptError 4:[1] ()

这类日志只能说明调用了一个空函数,却看不到函数名、脚本路径和出错行号。脚本数量较多时,几乎无法直接定位问题。

修改后,日志会变成:

error: attempt to call global `x888902_sYuanbaoShopCheckOp' (a nil value)
stack traceback:
   1: function `x888902_OpenYuanbaoShop' at line 28
      [file `/event/prize/yuanbaoshop.lua']
ScriptError 4:[1] (@/event/prize/yuanbaoshop.lua)

现在可以直接确认:

  • 未定义的函数是 x888902_sYuanbaoShopCheckOp
  • 调用者是 x888902_OpenYuanbaoShop
  • 出错位置是第 28 行
  • 所属脚本是 /event/prize/yuanbaoshop.lua

原因分析

问题不在 Lua 无法生成调用栈,而是原有代码把关键的信息丢掉了。

脚本加载流程大致为:

LuaInterface::LoadLua_File
    -> FoxLuaScript::LoadBuffer
        -> Lua_CompileBuffer

原代码编译 Lua chunk 时,把脚本名称传成了 NULL

Lua_CompileBuffer(m_LuaState, (char*)pBuffer, dwLen, NULL);

Lua 因此不知道当前代码来自哪个文件,运行错误时自然无法输出有效的脚本路径。

另一方面,Lua 已经通过 _ERRORMESSAGE 生成了包含函数名、行号和调用栈的完整错误信息,但 _ALERT 只把它输出到了标准错误:

fputs(luaL_check_string(L, 1), stderr);

写入 luaerror.log 的反而是 lua_error() 中较早产生的简略信息。最终就只剩下一句没有上下文的 attempt to call a nil value

一、加载脚本时保存真实路径

LuaInterface::LoadLua_File() 中,将脚本路径保存为 Lua chunk 名称:

CHAR chunkName[sizeof(mLua.m_szScriptName)];
memset(chunkName, 0, sizeof(chunkName));

chunkName[0] = '@';
strncpy(chunkName + 1, filename, sizeof(chunkName) - 2);

// Lua只把chunk名称用于报错定位,不影响脚本的加载和执行结果。
mLua.SetScriptName(chunkName);

BOOL bRet = mLua.LoadBuffer((BYTE*)szIn, nSize);

名称前面的 @ 是 Lua 的文件来源标记。比如:

@/event/prize/yuanbaoshop.lua

Lua 会将它识别为脚本文件,而不是一段匿名字符串。

这里使用固定长度缓冲区并预留字符串结束符,避免脚本路径过长时越界。

二、编译时把路径传给 Lua

修改 FoxLuaScript::LoadBuffer()

BOOL FoxLuaScript::LoadBuffer(PBYTE pBuffer, DWORD dwLen)
{
    const char* pChunkName =
        m_szScriptName[0] != '\0' ? m_szScriptName : NULL;

    if (Lua_CompileBuffer(
            m_LuaState,
            (char*)pBuffer,
            dwLen,
            pChunkName) != 0)
    {
        ScriptError(LUA_SCRIPT_COMPILE_ERROR);
        return FALSE;
    }

    return TRUE;
}

原来这里固定传入 NULL,即使上层知道脚本路径,Lua 调试信息中仍然没有文件来源。

改为传入 m_szScriptName 后,Lua 在编译函数时会记录:

  • chunk 对应的脚本文件
  • 每个函数所在的行号
  • 运行错误发生时的调用关系

原有的 ScriptError() 也会自动带上脚本名称:

ScriptError 4:[1] (@/event/prize/yuanbaoshop.lua)

不需要修改它的接口。

三、保存 Lua 已生成的完整调用栈

修改 lbaselib.c 中的 _ALERT

static int luaB__ALERT(lua_State *L)
{
    const char *message = luaL_check_string(L, 1);
    FILE *fp;

    fputs(message, stderr);

    /* 错误回调仍在Lua栈内,此处才能保留完整的函数名和行号。 */
    fp = fopen("./Log/luaerror.log", "a");
    if (fp != NULL)
    {
        fputs(message, fp);
        fclose(fp);
    }

    return 0;
}

关键点是必须在 _ALERT 中写日志。

错误到达这里时,Lua 的 _ERRORMESSAGE 已经把原始异常和调用栈整理成完整文本,例如:

error: attempt to call global `x888902_sYuanbaoShopCheckOp' (a nil value)
stack traceback:
   1: function `x888902_OpenYuanbaoShop' at line 28
      [file `/event/prize/yuanbaoshop.lua']

如果在更底层过早记录,只能拿到未经整理的原始错误内容。

四、删除重复的简略日志

原来的 lua_error() 会先写一次简略错误:

FILE* fp = fopen("./Log/luaerror.log", "a");
if (fp)
{
    fprintf(fp, "%s\n", s);
    fclose(fp);
}

现在完整错误已经统一在 _ALERT 中写入,因此删除这段代码:

LUA_API void lua_error(lua_State *L, const char *s)
{
    if (s)
        message(L, s);

    luaD_breakrun(L, LUA_ERRRUN);
}

否则同一个异常会同时出现简略日志和完整日志,既重复又容易干扰排查。

最终错误链路

修改后的处理流程为:

加载脚本
  -> 保存真实脚本路径
  -> 使用路径作为 Lua chunk 名称
  -> Lua 记录函数来源和行号
  -> 运行时发生异常
  -> _ERRORMESSAGE 生成完整 traceback
  -> _ALERT 写入 luaerror.log
  -> ScriptError 补充脚本执行状态

这次修改没有改变 Lua 函数参数、返回值、执行顺序和错误恢复流程,也不需要修改任何业务脚本。它只补齐了原本已经存在、但没有正确传递和落盘的调试信息。

部署时需要成套更新相关模块:

Windows:
Server.exe
LuaLib.dll
FoxScriptEngine.dll

Linux:
Server
libLuaLib.so
libFoxScriptEngine.so

更新后,再遇到 Lua 空函数、脚本调用链错误或者运行时异常,就可以直接根据 luaerror.log 中的脚本路径、函数名和行号定位问题。

您需要登录后才可以回帖 登录 | register

本版积分规则

QQ|手机版|雪舞知识库 ( 浙ICP备15015590号-1 | 萌ICP备20232229号|浙公网安备33048102000118号 )|天天打卡

GMT+8, 2026-8-30 10:58 , Processed in 0.068490 second(s), 26 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表