Erlang Elixir编译时代码注入/AOP
我以前使用过AOP风格的代码来分离逻辑和日志记录,并且对结果非常满意。我知道人们对AOP的看法各不相同,但我想在Elixir中找到一个解决方案,即使我最终没有在prod中使用它 我见过的最接近的例子是ExUnit内部的setup回调,它允许在每次测试运行之前执行代码;我想做一些类似的事情,但是还没能通过ExUnit源来理解那里的直觉 以代码形式:Erlang Elixir编译时代码注入/AOP,erlang,aop,elixir,Erlang,Aop,Elixir,我以前使用过AOP风格的代码来分离逻辑和日志记录,并且对结果非常满意。我知道人们对AOP的看法各不相同,但我想在Elixir中找到一个解决方案,即使我最终没有在prod中使用它 我见过的最接近的例子是ExUnit内部的setup回调,它允许在每次测试运行之前执行代码;我想做一些类似的事情,但是还没能通过ExUnit源来理解那里的直觉 以代码形式: defmodule Project.Logic do LoggingInjection.inject Project.Logging
defmodule Project.Logic do
LoggingInjection.inject Project.Logging
def work_do_stuff(arg) do
#...
#returns some_result
end
end
在单独的代码文件中:
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
最后,真正的问题是:启用此魔法的代码是什么
defmodule LoggingInjection do
defmacro inject(logging_module) do
#What goes here?
end
end
我想提出一种完全不同的方法来解决这个问题: ExUnit方法适用于ExUnit,因为它是一个测试框架,可以对测试的运行方式以及代码的编写方式施加限制。对于您的实际应用程序(包括日志记录和其他内容),基于事件的系统似乎是更健壮的解决方案,它也可以轻松地从并发中获益
这个想法是,您将启动一个GenEvent并向其发送事件。您的记录器将是安装在GenEvent中的处理程序。您可以选择将发布的事件设置为同步或异步。我们的文档中的例子正好是这种情况。 它不是AOP,但如果您对运行时的函数调用感兴趣,可以考虑查看Erlang跟踪。 例如,可以使用
:dbg
设置函数调用的动态跟踪。下面是如何跟踪对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)> ">>)
要使用它,只需启动它并提供要跟踪的模块列表:
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)> ")
iex(2)>跟踪程序启动([IO])
{:好的,#PID}
称为Elixir.IO.puts(:stdio,“\e[33m{:ok,#PID}\e[0m”)
Elixir.IO.puts/2返回正常
调用Elixir.IO.get(:stdio,“iex(3)>”)
跟踪功能非常强大,你可以用它做各种事情。我自己没有把它用于日志系统,所以我不能说它会有多大的问题,所以如果你走这条路,我建议你小心观察性能,因为太多的跟踪可能会使你的系统过载。我用同样的方法偶然发现了这个问题ed.不是我的解决方案,把它放在这里供大家参考,我发现了一个关于宏以及如何提取函数def信息的极好的方法 基本上:
import Kernel, except: [def: 2]
defmacro def(fn_call_ast, fn_opts_ast) do
result_fn_call_ast = process_call_ast fn_call_ast
result_fn_opts_ast = process_opts_ast fn_opts_ast
quote do
Kernel.def(
unquote(result_fn_call_ast), unquote(result_fn_opts_ast))
end
end
defmodule User do
use FunctionDecorating
decorate_fn_with LogDecorator
def say(word) do
word
end
end
iex>User.say("hello")
#PID<0.86.0> [x] Elixir.User.say(["hello"]) -> "hello"
"hello"
defmodule用户操作
使用功能装饰
用LogDecorator装饰
def说(词)做
单词
结束
结束
iex>User.say(“你好”)
#PID[x]Elixir.User.say([“你好])->“你好”
“你好”
如果您不介意用属性装饰函数,可以使用:
这很不显眼。你可以做这样的LogDecorator:
defmodule LogDecorator do
use Decorator.Define, [log: 0]
require Logger
def log(body, context) do
quote do
self = unquote(__MODULE__)
data =
unquote(Macro.escape(context))
|> Map.delete(:__struct__)
|> Map.merge(%{
args: self.listify(unquote(context.args)),
returned: unquote(body)
})
self.trace_function(data)
data.returned
end
end
def listify(arg) when is_list(arg), do: arg
def listify(arg) when is_map(arg), do: Enum.into(arg, [])
def listify(arg), do: [arg]
def trace_function(ctx) do
Logger.info("Function Invocation #{inspect ctx}")
end
end
defmodule MyModule do
use LogDecorator
@decorate log()
def square(a) do
a * a
end
@decorate log()
def add(a, b) do
a + b
end
end
要使用它,您需要将属性放置在要跟踪的函数上,如下所示:
defmodule LogDecorator do
use Decorator.Define, [log: 0]
require Logger
def log(body, context) do
quote do
self = unquote(__MODULE__)
data =
unquote(Macro.escape(context))
|> Map.delete(:__struct__)
|> Map.merge(%{
args: self.listify(unquote(context.args)),
returned: unquote(body)
})
self.trace_function(data)
data.returned
end
end
def listify(arg) when is_list(arg), do: arg
def listify(arg) when is_map(arg), do: Enum.into(arg, [])
def listify(arg), do: [arg]
def trace_function(ctx) do
Logger.info("Function Invocation #{inspect ctx}")
end
end
defmodule MyModule do
use LogDecorator
@decorate log()
def square(a) do
a * a
end
@decorate log()
def add(a, b) do
a + b
end
end
并发是这个方案的一个极好的补充,我很乐意采用GenEvent方法;我需要什么才能使它满足主要需求(日志记录与逻辑分离)是一种在每个方法调用的开始和结束时分派事件的方法;使这些事件包含参数/返回/方法信息;并在每个函数的开始/结束处隐藏所有样板文件。替换
GenEvent.notify
forLogging.log
的代码不能解决主要需求。好吧,您的代码does对日志记录一无所知。GenEvent旨在提供一个通用扩展点,该扩展点可供任何人使用,且不与实现耦合。在任何情况下,要直接回答您的问题,目前无法在编译时按照您的要求进行分离(我不知道库或示例)。:)