如何为函数表实现跟踪功能?

我有一个如下所示的表

local ftable = {
  getPinId = app.getPinId
}

ftable被传递到另一个函数中,该函数将其导出为RPC接口。 这个方法可行,但现在我想将函数调用跟踪添加到日志文件中。

简单的方法是

local ftable = {
  getPinId = function(...) print("getPinId") app.getPinId(...) end
}

但是,这不是特别好的方法。 我希望像这样放置:

local trace = function(func, ...)
   return function(...) print(func) func(...) end
end

local ftable = {
  getPinId = trace(app.getPinId)
}

但是,这并不能产生完全预期的结果。参数没有被传递。

另一个选项是使用这样的元表:

local ftable = {}
setmetatable(ftable, {
  __index = function(_, k)
  printf("Call: app.%s\n", k) return app[k] end
})

这是有效的。但如果可能,我也希望能打印传递的参数。

有什么建议吗? 我只使用luajit,如果有什么不同之处,请告诉我。

点赞
用户107090
用户107090

使用 __call 元方法:

M = {
    __call = function (t, ...)
        print("calling ", t.name, ...)
        return t.func(...)
    end
}

trace = function(func, name)
    return setmetatable({func=func, name=name}, M)
end

function f(...)
    print("in f", ...)
end

g = trace(f, "f")
g(10, 20, 30)
2016-06-17 01:37:27
用户3677376
用户3677376

在 Lua 中,包装函数调用很容易:

local function wrap( f )
  local function after( ... )
    -- 在函数调用 f 之后执行的代码
    print( "返回值:", ... )
    return ...
  end
  return function( ... )
    -- 在函数调用 f 之前执行的代码
    print( "参数:", ... )
    return after( f( ... ) )
  end
end

local function f( a, b, c )
  return a+b, c-a
end

local f_wrapped = wrap( f )
f_wrapped( 1, 2, 3 )

输出为:

参数:  1   2   3
返回值:  3   2

日志和追踪的一个问题是,Lua 值(包括函数)本身没有名称。调试库会尝试通过检查函数的调用或存储位置来找到适当的名称,但如果你想确保,你必须自己提供名称。但是,如果您的函数存储在(嵌套的)表中(如注释中所示),您可以编写一个函数,遍历嵌套的表,并使用表键作为名称包装所有找到的函数:

local function trace( name, value )
  local t = type( value )
  if t == "function" then -- 进行包装
    local function after( ... )
      print( name .. " 返回值:", ... )
      return ...
    end
    return function( ... )
      print( "调用 " .. name .. ":", ... )
      return after( value( ... ) )
    end
  elseif t == "table" then -- 递归进入子表
    local copy = nil
    for k,v in pairs( value ) do
      local nv = trace( name.."."..tostring( k ), v )
      if nv ~= v then
        copy = copy or setmetatable( {}, { __index = value } )
        copy[ k ] = nv
      end
    end
    return copy or value
  else -- 其他值被忽略(原样返回)
    return value
  end
end

local ftable = {
  getPinId = function( ... ) return "x", ... end,
  nested = {
    getPinId = function( ... ) return "y", ... end
  }
}

local ftableTraced = trace( "ftable", ftable )
ftableTraced.getPinId( 1, 2, 3 )
ftableTraced.nested.getPinId( 2, 3, 4 )

输出为:

调用 ftable.getPinId:    1   2   3
ftable.getPinId 返回值:    x   1   2   3
调用 ftable.nested.getPinId: 2   3   4
ftable.nested.getPinId 返回值: y   2   3   4

需要注意一些事项:

  1. 表键可以是任意 Lua 值,而不仅仅是完全由可打印字符组成的短字符串。
  2. 表可以包含循环引用。如果包含循环引用,上面的朴素实现将因堆栈溢出而崩溃。
2016-06-17 04:35:09