defmodule ProxmoxAgent.Diagnostics do @moduledoc """ Optional diagnostic dump of external commands and outgoing samples. Controlled by `:dump_dir` in the application env. When unset or nil, `log_command/4` and `log_sample/2` are no-ops and incur no I/O. When set, they cast to `ProxmoxAgent.Diagnostics.Writer` (if running) which serializes appends to `commands.log` and `samples.log`. """ require Logger @type command_result :: {:ok, String.t()} | {:error, term()} @spec configure(String.t() | nil) :: :ok def configure(nil), do: disable() def configure(""), do: disable() def configure(dir) when is_binary(dir) do case File.mkdir_p(dir) do :ok -> Application.put_env(:agent, :dump_dir, dir) Logger.info("diagnostics: enabled, writing to #{dir}") :ok {:error, reason} -> Logger.warning("diagnostics: mkdir_p #{dir} failed (#{inspect(reason)}); dumping disabled") disable() end end @spec enabled?() :: boolean() def enabled?, do: not is_nil(Application.get_env(:agent, :dump_dir)) @spec dump_dir() :: String.t() | nil def dump_dir, do: Application.get_env(:agent, :dump_dir) @spec log_command(String.t(), [String.t()], command_result(), non_neg_integer()) :: :ok def log_command(cmd, args, result, duration_us) do cast({:command, cmd, args, result, duration_us}) end @spec log_sample(String.t(), map()) :: :ok def log_sample(kind, payload) when is_binary(kind) and is_map(payload) do cast({:sample, kind, payload}) end @spec log_read(Path.t(), command_result(), non_neg_integer()) :: :ok def log_read(path, result, duration_us) do log_command("cat", [to_string(path)], result, duration_us) end defp disable do Application.delete_env(:agent, :dump_dir) :ok end defp cast(msg) do if enabled?() do case Process.whereis(ProxmoxAgent.Diagnostics.Writer) do nil -> :ok pid -> GenServer.cast(pid, msg) end end :ok end end