Elixir编译时代码注入/ AOP [英] Elixir compile-time code injection / AOP

查看:151
本文介绍了Elixir编译时代码注入/ 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 
使用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
pre>

要使用它,只需启动它提供您要追踪的模块列表:

  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 to IO:

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 simple gen_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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆