I threw an example together (also below) of how this might work. The LogWriter is only for testing purposes, it just periodically writes random lines to the log file in order to mimc a real log. LogReader will periodically consume all new lines in a log file and do something with them. I have them writing to stdout in the example, but this could be sending them a channel or another service.
I’m not sure what sort of performance implications (if any) are involved with this. This is more of a proof of concept. You might be better off using the file_system library so that you get notified directly of new lines instead of having to poll.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Log Reader - polls log file and sends new lines to channel
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
defmodule LogReader do
use GenServer
@log_file "priv/some_log.log"
@poll_interval 5 * 1000 # 5 seconds
def run_test() do
{:ok, _} = LogWriter.start_link()
{:ok, _} = LogReader.start_link()
end
def start_link(_ \\ []) do
GenServer.start_link(__MODULE__, :ok, name: LogReader)
end
def init(:ok) do
# open the log file and set the pointer to the end so that we only grab
# new log messages
{:ok, fp} = File.open(@log_file, [:read])
:file.position(fp, :eof)
poll()
{:ok, fp}
end
def handle_info(:read_log_lines, fp) do
# consume any new log lines and pass them off to the channel
fp |> read_til_eof |> send_to_channel
poll()
{:noreply, fp}
end
def read_til_eof(fp),
do: read_til_eof(IO.binread(fp, :line), fp, [])
def read_til_eof(:eof, _fp, buffer), do: buffer
def read_til_eof(line, fp, buffer),
do: read_til_eof(IO.binread(fp, :line), fp, buffer ++ [line])
# this could be handing off the new lines to some service or sending directly
# to the channel or whatever
def send_to_channel([]), do: :ok
def send_to_channel(lines),
do: for line <- lines, do: IO.puts line
defp poll(),
do: Process.send_after(self(), :read_log_lines, @poll_interval)
end
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Log Writer - simulates log writes
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
defmodule LogWriter do
use GenServer
@log_file "priv/some_log.log"
def start_link(_ \\ []) do
GenServer.start_link(__MODULE__, :ok, name: LogWriter)
end
def init(:ok) do
{:ok, fp} = File.open(@log_file, [:append])
poll()
{:ok, fp}
end
def handle_info(:write_log, fp) do
for _ <- 0..:rand.uniform(5), do: IO.puts fp, make_log_message()
poll()
{:noreply, fp}
end
def make_log_message() do
time = :os.system_time(:milli_seconds) |> to_string()
body = :crypto.strong_rand_bytes(15) |> Base.url_encode64
"[#{time}] #{body}"
end
defp poll() do
interval = :rand.uniform(10) * 1000
Process.send_after(self(), :write_log, interval)
end
end