erlang - अमृत ​​संकलन-समय कोड इंजेक्शन/एओपी




aop elixir (3)

मैंने पहले लॉगिंग से तर्क को अलग करने के लिए एओपी-शैली कोड का इस्तेमाल किया है, और परिणामों से बहुत प्रसन्न हुआ है। मैं समझता हूं कि एओपी पर राय अलग-अलग होती है, लेकिन मैं अमिक्सीर में एक समाधान का पता लगाना चाहूंगा, भले ही मैं इसका इस्तेमाल न करने पर भी करूँ।

मैंने देखा है सबसे निकटतम उदाहरण ExUnit के अंदर सेटअप कॉलबैक है, जो प्रत्येक परीक्षण से पहले कोड के निष्पादन की अनुमति देता है; मैं कुछ इसी तरह से करना चाहता हूं, लेकिन वहां मौजूद एक्जिट्स को समझने के लिए एक्सयूनेट स्रोत के माध्यम से उलझाव नहीं किया जा सकता है।

कोड फॉर्म में:

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

मैं इस समस्या के लिए एक पूरी तरह से अलग दृष्टिकोण पेश करना चाहूंगा : GenEvent

ExUnit रास्ता ExUnit के लिए काम करता है क्योंकि यह एक परीक्षण ढांचे है और यह कैसे परीक्षण चलाता है और इसलिए कैसे कोड को लिखा जाना चाहिए पर प्रतिबंध लगा सकते हैं आपके वास्तविक आवेदन के लिए, जिसमें लॉगिंग और अन्य चीजें शामिल हैं, एक इवेंट आधारित सिस्टम बहुत मजबूत समाधान है जो आसानी से संगामिति से आसानी से लाभ उठा सकता है।

विचार यह है कि आप एक GenEvent शुरू करेंगे और इसके लिए इवेंट्स भेजेंगे। आपके लकड़हारा जेएनईवेंट में एक हेन्डलर स्थापित होगा। आप प्रकाशित किए गए ईवेंट के लिए सिंक या एसिंक के लिए चुन सकते हैं हमारे दस्तावेज़ीकरण में उदाहरण इस मामले को पूरी तरह से शामिल करता है।


यह एओपी नहीं है, लेकिन अगर आप रनटाइम पर फ़ंक्शन कॉल्स देखने में दिलचस्पी रखते हैं, तो आप एर्लंग ट्रेस की तलाश में विचार कर सकते हैं।

उदाहरण के लिए, आप उपयोग कर सकते हैं :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, :[email protected], 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)> ">>)

मैं कभी-कभी एक चल रहे बीम नोड से कनेक्ट करने के लिए इस सुविधा का उपयोग करता हूं, और चल रहे सिस्टम का विश्लेषण करता हूं। एक बार जब आप कार्य पूरा कर :dbg.stop_clear साथ निशान को बंद करना सुनिश्चित करें :dbg.stop_clear

यदि आप मैन्युअल रूप से ट्रेस संदेशों को संभाल कर रखना चाहते हैं और उनके बारे में कुछ विशिष्ट करें (जैसे कि उन्हें किसी फ़ाइल में लॉग इन करें), तो आप इसका उपयोग कर सकते हैं :erlang.trace यहां एक सरल gen_server जो विभिन्न मॉड्यूलों को कॉल करता है:

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

इसका उपयोग करने के लिए, बस इसे शुरू करें और उन मॉड्यूलों की सूची प्रदान करें जिन्हें आप ढूंढना चाहते हैं:

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)> ")

अनुरेखण बहुत शक्तिशाली है और आप इसके साथ सभी प्रकार की चीज कर सकते हैं। मैंने इसे लॉगिंग सिस्टम के लिए उपयोग नहीं किया था, इसलिए मैं यह नहीं कह सकता कि एक समस्या कितनी होगी, इसलिए यदि आप इस सड़क को लेते हैं, तो मैं आपको सलाह देता हूं कि आप सावधानी बरतें और प्रदर्शन का पालन करें, क्योंकि बहुत अधिक अनुरेखण अपने सिस्टम को अधिभार दें


मैंने इस प्रश्न पर उसी आवश्यकता के साथ ठोकर खाई। मेरा समाधान नहीं, इसे किसी भी संदर्भ के लिए डालने के लिए, मुझे मैक्रोज़ पर एक उत्कृष्ट ट्यूटोरियल श्रृंखला मिली और कैसे सासा जुरीक द्वारा फ़ंक्शन डीएफ़ जानकारी को निकालने के लिए और उसके द्वारा 'डीईफ़' मैक्रो को ओवरराइड करने का एक तरीका भी मिला।

मूल रूप से:

  1. फ़ंक्शन घोषणा को सजाने के लिए, आप कर्नेल मॉड्यूल से 'def' मैक्रो के डिफ़ॉल्ट आयात को बाहर कर सकते हैं।

    import Kernel, except: [def: 2] 
  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"






elixir