defmodule Forum.LogStore do @moduledoc """ Persists request and hosting logs to a JSON file. """ use GenServer @max_entries 1_000 def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end def add(entry) when is_map(entry) do GenServer.cast(__MODULE__, {:add, entry}) end def list(limit \\ 200) do GenServer.call(__MODULE__, {:list, limit}) end @impl true def init(_opts) do path = Path.expand("priv/logs/requests.json", File.cwd!()) File.mkdir_p!(Path.dirname(path)) {:ok, %{path: path, entries: read_entries(path)}} end @impl true def handle_cast({:add, entry}, state) do entry = entry |> Map.put_new("id", System.unique_integer([:positive, :monotonic])) |> Map.put_new("time", DateTime.utc_now() |> DateTime.to_iso8601()) entries = [entry | state.entries] |> Enum.take(@max_entries) write_entries!(state.path, entries) {:noreply, %{state | entries: entries}} end @impl true def handle_call({:list, limit}, _from, state) do {:reply, Enum.take(state.entries, limit), state} end defp read_entries(path) do with {:ok, raw} <- File.read(path), {:ok, entries} when is_list(entries) <- Jason.decode(raw) do entries else _ -> [] end end defp write_entries!(path, entries) do tmp_path = path <> ".tmp" File.write!(tmp_path, Jason.encode!(entries, pretty: true)) File.rename!(tmp_path, path) end end