Could I leverage Elixir’s metaprogramming capabilities to automatically annotate all functions with a given name?

Hey there!

I am currently trying to add logging capabilities to GenServer in Elixir. My current vision is that I write my own module (i.e. LoggingGenServer) and simply replace the use GenServer by use LoggingGenServer. This way, I can easily add logging capabilities to existing code as long as it made use of GenServer in the first place.

My approach to tackle this issue is two-fold:

  1. For all simple function calls, such as call, cast, start_link, etc I can simply define my own function which first does the logging and then calls the regular GenServer function to handle the rest of the behavior. This works.

  2. For the callbacks, such as handle_cast, handle_info, terminate, etc, it seems to be more difficult as I cannot just use the same solution as I used above. In essence, I need a way to add my own line of code before/after any such function a developer defines in his own modules. I found this Interceptor library, which seems to offer exactly that. However, this still requires the functions I want to intercept to be annotated or put in their special function. This is what I mainly need help with.

I’m pretty new to Elixir, but I do have some experience with metaprogramming. Could I leverage some of Elixir’s metaprogramming capabilities (e.g. macro’s) to automatically annotate all functions with a given name? That would, I think, solve my problem.

Kind regards & thanks for the help in advance!

In principle, you could likely do something like Interceptor.Annotated but without the filter_not_intercepted step. I do not know what sort of consequences that would have, but there may be a reason the library only ships with opt-in beyond a stylistic preference for explicitness.

HOWEVER

The BEAM has tools that can do that kind of function-call tracking without source-code modification, starting from the :erlang.trace/3 function.

Here’s a good introduction to tracing in Elixir from Erlang Solutions.

Here’s an example of using tracing to assert on messages arriving in other processes in a GenServer’s tests

3 Likes

Thanks for your response!

I’ve looked into the Tracer Module already, but it did not seem to offer all functionality I desire. Concretely I want to be able to:

  • Track creation (+ linkage) of an actor
  • Track the termination of an actor with status
  • Track message casts, calls and other sends
  • Track arrival of messages (when it is put into the mailbox)
  • Track receive of messages (when it is taken out of the mailbox and processed)
  • Track when a message is done receiving (when it is completely handled)

Ideally, I later also want to be able to introspect messages/actors I am tracking. For example, viewing their contents or state.

In regards to removing the filter_not_intercepted step, won’t this result in me intercepting all methods? Ideally I would only like to intercept functions with a specific name. Although I will probably be able to do the filtering on the intercepting side as well. I’ll take a look at it later today.

That almost sounds like you need OpenTelemetry to me, for what it’s worth.