Elixir编译时代码注入/ AOP [英] Elixir compile-time code injection / AOP
问题描述
我以前使用AOP风格的代码将Log与Logging分离开来,对结果非常满意。我认识到对AOP的看法有所不同,但是我想在Elixir中找出一个解决方案,即使我不会在产品中使用它。
我看到的最接近的例子是ExUnit内部的安装回调,允许在每次测试运行之前执行代码;我想做类似的事情,但是不能混淆ExUnit的来源来掌握那里的直觉。
在代码格式中:
defmodule Project.Logic do
LoggingInjection.inject Project.Logging
def work_do_stuff(arg)do
#...
#returns some_result
end
end
在单独的代码文件中:
defmodule Project.Logging do
#在Project.Logic之前调用。 work_do_stuff与相同的参数
def before_work_do_stuff(arg)do
Log.write(about to work_do_stuff with#{inspect arg})
end
#def after_work_do_stuff(some_result)隐含定义为no-op,
#但可以被覆盖。
end
最后,真正的问题:启用这个魔法的代码是什么?
defmodule LoggingInjection do
defmacro inject(logging_module)do
#
end
end
不是AOP,但是如果您有兴趣在运行时观察函数调用,可以考虑查看Erlang跟踪。
例如,您可以使用:dbg
来设置函数调用的动态跟踪。以下是跟踪所有调用 IO
的调用方式:
iex> :dbg.tracer
iex> :dbg.p(:all,[:call])
iex> :dbg.tp(IO,[{_ _,[],[{:return_trace}]})
(< 0.53.0>)调用'Elixir.IO':puts(stdio ,<\e [33m {:ok,[{:matched,:nonode @ nohost,28},{:saved,1}]} \e [0m>>)$ b $从Elixir.IO返回的b(<0.53.0):puts / 2 - > ok
(< 0.59.0>)调用'Elixir.IO':gets(stdio,<iex(4)>>>)
我偶尔使用此功能连接到正在运行的BEAM节点,并分析正在运行的系统。完成后,请务必停止跟踪:dbg.stop_clear
。
如果要手动处理跟踪消息并对其进行特定操作(例如将其记录到文件中),可以使用:erlang.trace
。这是一个简单的 gen_server
,可追踪到各种模块的调用:
defmodule Tracer do
pre>
使用GenServer
def start(modules),do:GenServer.start(__ MODULE__,modules)
def init(modules)do
: erlang.trace(:all,true,[:call])
for module< - modules do
:erlang.trace_pattern({module,:_,:_},[{ :_,[],[{:return_trace}]}])
end
{:ok,nil}
end
def handle_info #{fun}(#{trace,_,:call,{mod,fun,args}},state)do
IO.putscalled#{mod}#{fun}(#{Enum.join(Enum.map args& inspect / 1),,)})
{:noreply,state}
end
def handle_info({:trace,_,:return_from ,{mod,fun,arity},res},state)do
IO.puts#{mod}。#{fun} /#{arity} returned#{res}
{:noreply ,state}
end
def handle_info(_,state),do:{:noreply,state}
end
要使用它,只需启动它提供您要追踪的模块列表:
iex(2)> Tracer.start([IO])
{:ok,#PID< 0.84.0>}
称为Elixir.IO.puts(:stdio,\e [33m {:确定,#PID< 0.84.0>} \e [0m)
Elixir.IO.puts / 2返回OK
调用Elixir.IO.gets(:stdio,iex(3)> ;)
跟踪非常强大,你可以做各种各样的事情。我没有使用它作为一个测井系统,所以我不能说有多少问题,所以如果你走这条路,我建议你小心和观察表现,因为太多的追踪可能超载您的系统。
I've previously used AOP-style code to separate Logic from Logging, and been very pleased with the results. I recognize that opinions on AOP vary, but I'd like to figure out a solution in Elixir, even if I don't end up using it in prod.
The closest example I've seen is the setup callback inside of ExUnit, which allows execution of code before each test runs; I'd like to do something similar, but haven't been able to muddle through the ExUnit source to grasp the intuitions there.
In code form:
defmodule Project.Logic do LoggingInjection.inject Project.Logging def work_do_stuff(arg) do #... #returns some_result end end
in a separate code file:
defmodule Project.Logging do #called just before Project.Logic.work_do_stuff with the same args def before_work_do_stuff(arg) do Log.write("about to work_do_stuff with #{inspect arg}") end # def after_work_do_stuff(some_result) implicitly defined as no-op, # but could be overridden. end
and finally, the real question: What is the code to enable this magic?
defmodule LoggingInjection do defmacro inject(logging_module) do #What goes here? end end
解决方案It's not AOP, but if you're interested in observing function calls at runtime, you can consider looking into Erlang trace.
For example, you can use
:dbg
to setup dynamic traces of function calls. Here's how to trace all calls toIO
:iex> :dbg.tracer iex> :dbg.p(:all, [:call]) iex> :dbg.tp(IO, [{:_, [], [{:return_trace}]}]) (<0.53.0>) call 'Elixir.IO':puts(stdio,<<"\e[33m{:ok, [{:matched, :nonode@nohost, 28}, {:saved, 1}]}\e[0m">>) (<0.53.0>) returned from 'Elixir.IO':puts/2 -> ok (<0.59.0>) call 'Elixir.IO':gets(stdio,<<"iex(4)> ">>)
I occasionally use this feature to connect to a running BEAM node, and analyze the running system. Be sure to stop traces with
:dbg.stop_clear
once you're done.If you want to manually handle trace messages and do something specific about them (e.g. log them to a file), you can use
:erlang.trace
. Here's a simplegen_server
that traces calls to various modules:defmodule Tracer do use GenServer def start(modules), do: GenServer.start(__MODULE__, modules) def init(modules) do :erlang.trace(:all, true, [:call]) for module <- modules do :erlang.trace_pattern({module, :_, :_}, [{:_, [], [{:return_trace}]}]) end {:ok, nil} end def handle_info({:trace, _, :call, {mod, fun, args}}, state) do IO.puts "called #{mod}.#{fun}(#{Enum.join(Enum.map(args, &inspect/1), ",")})" {:noreply, state} end def handle_info({:trace, _, :return_from, {mod, fun, arity}, res}, state) do IO.puts "#{mod}.#{fun}/#{arity} returned #{res}" {:noreply, state} end def handle_info(_, state), do: {:noreply, state} end
To use it, just start it and provide the list of modules you want to trace:
iex(2)> Tracer.start([IO]) {:ok, #PID<0.84.0>} called Elixir.IO.puts(:stdio,"\e[33m{:ok, #PID<0.84.0>}\e[0m") Elixir.IO.puts/2 returned ok call Elixir.IO.gets(:stdio,"iex(3)> ")
Tracing is very powerful and you can do all sorts of thing with it. I didn't use it for a logging system myself, so I can't say how much of a problem it will be, so if you take this road, I advise you to be careful and observe performance, because too much of tracing might overload your system.
这篇关于Elixir编译时代码注入/ AOP的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!