【问题标题】:How to implement a trace function for table to functions?如何实现表到函数的跟踪功能?
【发布时间】:2016-06-17 01:19:31
【问题描述】:

我有一张这样的桌子

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。

【问题讨论】:

  • 你的代码应该可以工作,除了它会打印函数的地址,而不是它的名字。请展示您报告的失败示例。
  • 是的,我尝试了 debug.getinfo 函数,但没有帮助。它把它当作一个匿名函数。我想我可以通过名字。现在我正在使用 metatable 方法,它大大缩短了代码。
  • Lua 函数和所有值一样,没有名称。这就是为什么@lhf 在他的答案中将字符串与函数值相关联。

标签: lua luajit


【解决方案1】:

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

local function wrap( f )
  local function after( ... )
    -- code to execute *after* function call to f
    print( "return values:", ... )
    return ...
  end
  return function( ... )
    -- code to execute *before* function call to f
    print( "arguments:", ... )
    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 )

输出是:

arguments:  1   2   3
return values:  3   2

记录/跟踪的一个问题是 Lua 值(包括函数)本身没有名称。调试库试图通过检查函数的调用方式或存储位置来为函数找到合适的名称,但如果您想确定,您必须自己提供一个名称。但是,如果您的函数存储在(嵌套)表中(如注释中所示),您可以编写一个迭代嵌套表的函数,并使用表键作为名称包装它找到的所有函数:

local function trace( name, value )
  local t = type( value )
  if t == "function" then -- do the wrapping
    local function after( ... )
      print( name.." returns:", ... )
      return ...
    end
    return function( ... )
      print( "calling "..name..":", ... )
      return after( value( ... ) )
    end
  elseif t == "table" then -- recurse into subtables
    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 -- other values are ignored (returned as is)
    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 )

输出是:

calling ftable.getPinId:    1   2   3
ftable.getPinId returns:    x   1   2   3
calling ftable.nested.getPinId: 2   3   4
ftable.nested.getPinId returns: y   2   3   4

需要注意的一些事项:

  1. 表键可以是任意 Lua 值,而不仅仅是完全由可打印字符组成的短字符串。
  2. 表可以包含循环引用。如果他们这样做了,上面的幼稚实现将因堆栈溢出而死。

【讨论】:

    【解决方案2】:

    改用 __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)
    

    【讨论】:

    • 使用元方法 __index 方法非常方便,因为我不需要显式地键入每个方法。有没有办法将两者结合起来。即用它添加一个 __call 方法?
    • @Matt,试试setmetatable(ftable,{__index=function(t, k) local f=trace(app[k],k); t[k]=f; return f end})
    • 嗯,不知道为什么,但在我的情况下这不起作用。我的ftable有点复杂。它包括表格中的表格,但我将其附加到表格中的表格。哦,好吧,我会留到另一天弄清楚。
    猜你喜欢
    • 1970-01-01
    • 2017-03-12
    • 2022-12-29
    • 1970-01-01
    • 2013-01-09
    • 2013-07-27
    • 1970-01-01
    • 1970-01-01
    • 2011-04-17
    相关资源
    最近更新 更多