2017/1/1 18:14:40

[string "scenes/game/home/MapView.lua"]:0: table index is nil
stack traceback:
 [string "scenes/game/home/MapView.lua"]: in function 'onEnter'
 [string "framework/cocos2dx/NodeEx.lua"]: in function <[string "framework/cocos2dx/NodeEx.lua"]:0>
 [C]: in function 'addChild'
 [string "scenes/game/Game.lua"]: in function '__navigateTo'
 [string "scenes/game/Game.lua"]: in function 'navigateBack'
 [string "scenes/game/daily/Activeness.lua"]: in function <[string "scenes/game/daily/Activeness.lua"]:0>

以上我们看到一段脚本报错信息

cocos/lua开发模式中,分析错误信息是十分必要的

在引擎框架下脚本运行时错误会自动打印这样的信息

其实这些信息并不是"凭空"或者"自动"产生的

下面就分析一下lua脚本错误信息的产生和收集

转载请注明本文地址

 

 

1.The Debug Library

debug是lua自带的库函数(类似)

包含脚本运行错误产生的信息

debug.getInfo/debug.traceback可以逐条/全部返回错误发生处的堆栈信息

以上打印的信息就是通过debug.traceback获取的

cocos2d-x-3.5\cocos\scripting\lua-bindings\script\init.lua

文件中定义了一个全局函数



__G__TRACKBACK__ = function(msg)
    local msg = debug.traceback(msg, 3)
    print(msg)
    return msg
end



debug.traceback(msg , 3)会返回第3层and3层以下的栈信息,并把msg拼接在了最前面

比如我从一个匿名函数开始,调用f1,f1中调用f2,f2中调用f3,f3再调用f4

f4中写一个错误的语句a=aa.aaa

错误的信息是attemp to index global aa(a nil value)

它会被拼接在最前面

堆栈信息大概是这样的



in function __index

in function f4

in function f3

in function f2

in function f1



但是注意,debug.traceback(msg , 3)这里的参数3表示返回从第三层开始的信息

也就是说

in function __index

in function f4

这两条是不会被打印的

cocos这样做有点让我不理解

debug.traceback(msg , level)level可以不传,默认是1,即从顶层返回错误信息

越上层越接近错误发生的位置,应该是更加重要的

 

2.lua_pcall



int LuaStack::executeFunction(int numArgs)
{
    int functionIndex = -(numArgs + 1);
    if (!lua_isfunction(_state, functionIndex))
    {
        CCLOG("value at stack [%d] is not function", functionIndex);
        lua_pop(_state, numArgs + 1); // remove function and arguments
        return 0;
    }

    int traceback = 0;
    lua_getglobal(_state, "__G__TRACKBACK__");                         /* L: ... func arg1 arg2 ... G */
    if (!lua_isfunction(_state, -1))
    {
        lua_pop(_state, 1);                                            /* L: ... func arg1 arg2 ... */
    }
    else
    {
        lua_insert(_state, functionIndex - 1);                         /* L: ... G func arg1 arg2 ... */
        traceback = functionIndex - 1;
    }
    
    int error = 0;
    ++_callFromLua;
    error = lua_pcall(_state, numArgs, 1, traceback);                  /* L: ... [G] ret */
    --_callFromLua;
    if (error)
    {
        if (traceback == 0)
        {
            CCLOG("[LUA ERROR] %s", lua_tostring(_state, - 1));        /* L: ... error */
            lua_pop(_state, 1); // remove error message from stack
        }
        else                                                            /* L: ... G error */
        {
            lua_pop(_state, 2); // remove __G__TRACKBACK__ and error message from stack
        }
        return 0;
    }
    
    // get return value
    int ret = 0;
    if (lua_isnumber(_state, -1))
    {
        ret = (int)lua_tointeger(_state, -1);
    }
    else if (lua_isboolean(_state, -1))
    {
        ret = (int)lua_toboolean(_state, -1);
    }
    // remove return value from stack
    lua_pop(_state, 1);                                                /* L: ... [G] */
    
    if (traceback)
    {
        lua_pop(_state, 1); // remove __G__TRACKBACK__ from stack      /* L: ... */
    }
    
    return ret;
}



当我们执行一个lua函数时,

如点击按钮,按钮绑定了lua回调函数

cocos会执行以上操作(传到了C++里再通过api调用lua函数)

这里首先找到__G__TRACKBACK__全局函数

并将它作为错误处理函数,插入到合适的位置并记录为traceback

 

error = lua_pcall(_state, numArgs, 1, traceback);

执行到这里时,若捕获到了错误,即在__G__TRACKBACK__中打印输出

之后if (traceback == 0)这里判定显然为false

...

后面就不多解释了

 

详细的流程参考lua_pcall的定义

lua_pcall

lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);


Calls a function in protected mode.

Both nargs and nresults have the same meaning as in lua_call. If there are no errors during the call, lua_pcall behaves exactly likelua_call. However, if there is any error, lua_pcall catches it, pushes a single value on the stack (the error message), and returns an error code. Like lua_calllua_pcall always removes the function and its arguments from the stack.

If errfunc is 0, then the error message returned on the stack is exactly the original error message. Otherwise, errfunc is the stack index of an error handler function. (In the current implementation, this index cannot be a pseudo-index.) In case of runtime errors, this function will be called with the error message and its return value will be the message returned on the stack by lua_pcall.

Typically, the error handler function is used to add more debug information to the error message, such as a stack traceback. Such information cannot be gathered after the return of lua_pcall, since by then the stack has unwound.

The lua_pcall function returns 0 in case of success or one of the following error codes (defined in lua.h):

  • LUA_ERRRUN: a runtime error.
  • LUA_ERRMEM: memory allocation error. For such errors, Lua does not call the error handler function.
  • LUA_ERRERR: error while running the error handler function.

 

3.总结与改进

cocos这样的脚本错误信息捕获是很有用的

作为改进,

可把debug.traceback(msg , 3)

改为debug.traceback(msg)

同时可以把traceback保存在本地并在合适时机上传服务端

收集玩家发生的错误信息

 

参考文献

http://manual.luaer.cn/

http://www.xker.com/page/e2015/05/191667.html