Elixir装饰器实现Appsignal埋点时编译时socket为nil,如何运行时取值?
问题根源
你的代码报错是因为get_socket函数是在编译阶段执行的:你在quote块内用unquote(get_socket(context))的写法,会在编译期直接调用get_socket获取结果再插入生成的代码中。此时context.args拿到的是函数参数的AST语法节点,不是运行时的实际参数值,用IEx.Info.info检查编译期的AST节点永远不可能匹配到运行时才存在的Socket结构体,必然返回nil。
解决方案
把Socket查找逻辑放到quote块内部,改为运行时执行,直接遍历函数的实际入参匹配Socket结构体即可,无需在编译期做判断。
通用实现(不依赖参数位置,适配所有LiveView回调)
import Appsignal.Phoenix.LiveView, only: [live_view_action: 4] def instrument(name, body, context) do # 仅提取参数的AST,编译期不做逻辑处理 args_ast = context.args quote bind_quoted: [name: name, module: context.module, args_ast: args_ast] do # 运行时遍历实际入参查找Socket socket = Enum.find(args_ast, fn %Phoenix.LiveView.Socket{} = sock -> sock _ -> false end) live_view_action(module, name, socket, fn -> unquote(body) end) end end
精简实现(仅适配handle_event,性能更高)
LiveView的handle_event回调参数顺序是固定的,第三个参数永远是Socket,可以直接提取对应位置的AST,省去运行时遍历开销:
import Appsignal.Phoenix.LiveView, only: [live_view_action: 4] def instrument(name, body, context) do socket_ast = Enum.at(context.args, 2) quote bind_quoted: [name: name, module: context.module, socket_ast: socket_ast] do live_view_action(module, name, socket_ast, fn -> unquote(body) end) end end
两种实现都完全兼容你期望的使用方式,不需要修改业务代码里的handle_event逻辑。
内容的提问来源于stack exchange,提问作者Sergio Tapia
相关产品推荐
相关产品推荐

