larshei

larshei

Extract n-th argument of all function calls in a project

I have a number of different notifications, each identified by an id (example “ticket_closed”).

This ID can later be used to select the right E-Mail Template for the right language etc.

So basically, the recipient, the notification id and some metadata is stored as an Oban Job,
and the E-Mail is sent from there.

I would love to automatically extract all the available notification IDs, for 2 reasons:

  1. I could then check if I have an E-Mail template for each ID and
  2. I can check if any references to notification IDs are actually referring to existing IDs

I am pretty sure this is a rather simple task with mix/elixir, but so far I could not figure it out.

How could I extract all calls to a function and the functions n-th argument?


I found the tracers: compile options, however I am not sure how to add a Tracer inside a project that is meant to trace the project itselft (and would therefore needs itself compiled before it can compile itself?)

Marked As Solved

dimitarvp

dimitarvp

You can try using GitHub - ast-grep/ast-grep: ⚡A CLI tool for code structural search, lint and rewriting. Written in Rust.

Given this input on my machine:

# Put this in f.ex. `test.ex`

defmodule Notifications.External do
  def schedule(user_id, link, notification_type, metadata) do
    IO.puts(
      "Scheduling stuff for user #{user_id}: " <>
        " link=#{link}, " <>
        "notification_type=#{notification_type}, " <> "metadata=#{inspect(metadata)}"
    )
  end
end

defmodule UserModuleOne do
  def function_one() do
    Notifications.External.schedule(1, "nope", :ticket_created, %{})
  end
end

defmodule UserModuleTwo do
  def function_two() do
    Notifications.External.schedule(2, "never", :ticket_updated, %{})
  end
end

defmodule UserModuleThree do
  def function_three() do
    Notifications.External.schedule(3, "not_happening", :ticket_closed, %{})

    task1 =
      Task.async(fn ->
        Notifications.External.schedule(4, "gone", :ticket_removed, %{})
      end)

    task2 =
      Task.async(fn ->
        Notifications.External.schedule(5, "you_serious?", :ticket_closed, %{})
      end)

    Task.await_many([task1, task2])
  end
end

Then you just run this:

sg -l ex -p 'Notifications.External.schedule($USER_ID, $LINK, $TYPE, $METADATA)' --rewrite '$TYPE' --json=compact | jq -r '.[] | .replacement' | sort | uniq

And that gives you:

:ticket_closed
:ticket_created
:ticket_removed
:ticket_updated

NOTE: it’s not perfect f.ex. if you have code that does alias Notifications.External and then calls External.schedule(...) then this incantation will not catch it. Though you can also make variants for any levels of nesting i.e. replace the pattern Notifications.External.schedule($USER_ID, $LINK, $TYPE, $METADATA) with External.schedule($USER_ID, $LINK, $TYPE, $METADATA) and you should be good. (And then you should be doing a union of all these results which is pretty easy with cat and then doing sort | uniq again.)

Also Liked

LostKobrakai

LostKobrakai

Boundary seems to use a custom compiler (Mix.Task.Compiler implementation) which registeres itself as the tracer module. That probably makes mix load that module, before compilation of the project itself.

alvises

alvises

@larshei I just saw you don’t need a tracer, you can ignore the post below (I didn’t find a way to delete my post :sweat_smile: )


For debugging purpose, for an app at runtime, I’ve used on a running distributed cluster the :dbg module, which is really simple to use.

# tracing handler
handler = fn {_, pid, _, {module, fun, args}, timestamp}=_data, trace_name ->
    IO.inspect({pid, module, fun, args, timestamp, trace_name})
end

# Start tracer with a handler
:dbg.tracer(:process, {handler, "my tracer"})

#  trace pattern. This function enables call trace for one or more functions.
:dbg.tp(String, :downcase, 1, [])

# traces :all
# :all -> All processes and ports in the system as well as all processes and ports created hereafter are to be traced.
# :call -> traces global function calls for the process according to the trace patterns set in the system
# :timestamp -> Includes a time stamp in all trace messages.
:dbg.p(:all, [:call, :timestamp])
iex> String.downcase("Hello")
{#PID<0.111.0>, String, :downcase, ["hello"], {1704, 212048, 422855},
 "my tracer"}
"hello"

https://www.erlang.org/doc/man/dbg#tracer-2
https://www.erlang.org/doc/man/dbg#tp-2
https://www.erlang.org/doc/man/dbg#p-2

Where Next?

Popular in Questions Top

bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

We're in Beta

About us Mission Statement