2
votes

These are my first 2 hours of Elixir so it might be a noobish question but I have some issues with spawning up a GenServer and casting it. I'm using this module which watches for changes in a file in my system and, each time a new entry is added in that file, I want to cast the log to a gen server which analyzes it and does whatever is supposed to do based on it's findings.

Here's the module that watches the changes:

defmodule Test.Watcher do
    use ExFSWatch, dirs: ["/var/log/"]

    import Test.Receiver, only: [analyze_log: 2]

    def callback(file_path, actions) do
        if file_path == "/var/log/syslog" do
            if :modified in actions do
                #~~~~ WHERE DO I GET THIS pid FROM?
                analyze_log pid, get_log
            end
        end
    end

    def get_log do
        {log, _} = System.cmd("tail", ["-n", "1", "/var/log/syslog"])
        log
    end
end

The watcher works very well and it receives the new logs but I'm having a problem with Test.Receiver which uses GenServer.

My first question is...where do I start this gen server? ExFSWatch has it's own start method and I can't override that. Do I call start_link each time a new log passes in (I doubt that but I had to ask)?

Ny all the examples I read I'm supposed to start it somewhere else, grab it's pid and pass it as a param to the analyze_log method so, if I'm right, my only remaining problem is finding out a good place to start the Test.Receiver Genserver and grabbing it's pid so I can use it inside the callback method.

defmodule Test.Receiver do
    use GenServer

    def start_link do
        GenServer.start_link(__MODULE__, :ok, [])
    end

    def analyze_log(pid, log) do
        GenServer.cast(pid, {:analyze_log, log})
    end

    def init(:ok) do
        {:ok, %{}}
    end

    def handle_cast({:analyze_log, log}, state) do
        IO.puts log
        {:noreply, state}
    end
end
1
Most modules that have such callback interface also have an extra "state" argument to carry such information around but it looks like for some reason ExFSWatch doesn't have one. The simplest way I can think of other than that is to spawn and register the Receiver process with a name and call it by name. - Dogbert
I formed an answer based on your comment as it helped. Thanks. - Romeo Mihalcea

1 Answers

0
votes

Ok, to expand on @Dogbert's answer and clear it out for others who are starting with Elixir I will provide the solution that worked out for me. It may not be the most elegant one as I lack the knowledge yet but works.

As Dogbert said, you need to register your GenServer with a name and call it by that name. So I went to my main file and spawned a supervisor which keeps these listeners up and active like so:

defmodule Test do
    use Application

    def start(_type, _args) do
        import Supervisor.Spec, warn: false

        Fail2ban.Watcher.start

        children = [
            # ... I have more here but let's keep it simple
            supervisor(Test.Receiver, [Test.Receiver]),
        ]

        opts = [strategy: :one_for_one, name: Test.Supervisor]
        Supervisor.start_link(children, opts)
    end
end

As you can see, the second parameter passed onto the supervisor call is the name under which I want to register this module. It may be anything you like I presume since I'm not very sure on that. This second parameter goes into my receiver's start_link and registers under that name:

def start_link(name \\ nil) do
    GenServer.start_link(__MODULE__, nil, [name: name])
end

With this in place now remember this method on the receiver that I had problems regarding the pid which I didn't know where to get from:

defmodule Test.Watcher do
    use ExFSWatch, dirs: ["/var/log/"]

    import Test.Receiver, only: [analyze_log: 2]

    def callback(file_path, actions) do
        if file_path == "/var/log/syslog" do
            if :modified in actions do
                # Instead of the pid, pass the name under which it was registered
                analyze_log Test.Receiver, get_log
            end
        end
    end

    def get_log do
        {log, _} = System.cmd("tail", ["-n", "1", "/var/log/syslog"])
        log
    end
end

Works for me, open to discussion and improvements. It's my 2nd day now of Elixir and I started to grasp it's ways of ticking. Coming from Python, Ruby, PHP myself it's a little hard different grabbing and passing information as parameters and state all the time but..I see the sun now.