Current section
Files
Jump to
Current section
Files
lib/metrixwire/config.ex
defmodule MetrixWire.Config do
@moduledoc """
Resolved configuration. Built once at application boot from `config :metrixwire,
...` merged with ENV fallbacks (`METRIXWIRE_KEY`, `METRIXWIRE_ENDPOINT`,
`METRIXWIRE_ENABLED`). Nothing here throws — bad input degrades to a sensible
default so instrumentation never breaks the host app.
Held in `:persistent_term` for lock-free, low-cost reads from the hot path (every
telemetry event reads config).
"""
@default_endpoint "http://localhost:3000/ingest"
@default_flush_interval_ms 5000
@default_timeout_ms 3000
@default_max_batch 20
@pt_key {__MODULE__, :config}
defstruct api_key: "",
endpoint: @default_endpoint,
flush_interval_ms: @default_flush_interval_ms,
timeout_ms: @default_timeout_ms,
max_batch: @default_max_batch,
capture_source: true,
enabled: false
@type t :: %__MODULE__{}
@doc """
Build the config from application env + ENV fallbacks and store it. Idempotent —
called once at boot. Returns the built struct.
"""
def load do
app = Application.get_all_env(:metrixwire)
api_key = to_s(fetch(app, :api_key, env("METRIXWIRE_KEY")))
endpoint = normalize_endpoint(fetch(app, :endpoint, env("METRIXWIRE_ENDPOINT")))
enabled_opt =
case Keyword.fetch(app, :enabled) do
{:ok, v} -> truthy(v)
:error -> env_enabled()
end
config = %__MODULE__{
api_key: api_key,
endpoint: endpoint,
flush_interval_ms: to_i(fetch(app, :flush_interval_ms, nil), @default_flush_interval_ms),
timeout_ms: to_i(fetch(app, :timeout_ms, nil), @default_timeout_ms),
max_batch: to_i(fetch(app, :max_batch, nil), @default_max_batch),
capture_source: fetch(app, :capture_source, true) != false,
# No key ⇒ disabled mode (never throw). Matches the other SDKs.
enabled: enabled_opt and api_key != ""
}
:persistent_term.put(@pt_key, config)
config
rescue
_ ->
config = %__MODULE__{}
:persistent_term.put(@pt_key, config)
config
end
@doc "The current config struct (default if not yet loaded)."
def get do
:persistent_term.get(@pt_key, %__MODULE__{})
rescue
_ -> %__MODULE__{}
end
@doc "Whether the SDK is enabled."
def enabled?, do: get().enabled
@doc "Accept a base URL too: append /ingest when missing."
def normalize_endpoint(url) do
url = to_s(url)
url = if url == "", do: @default_endpoint, else: url
url = String.replace(url, ~r{/+$}, "")
if String.ends_with?(url, "/ingest"), do: url, else: url <> "/ingest"
rescue
_ -> @default_endpoint
end
# ── helpers ──────────────────────────────────────────────────────────────
defp fetch(app, key, default) do
case Keyword.fetch(app, key) do
{:ok, nil} -> default
{:ok, v} -> v
:error -> default
end
end
defp env(key) do
case System.get_env(key) do
nil -> nil
"" -> nil
v -> v
end
end
defp env_enabled do
case env("METRIXWIRE_ENABLED") do
nil -> true
raw -> String.downcase(String.trim(raw)) not in ~w(false 0 no off)
end
end
defp truthy(v), do: v not in [false, nil, "false", "0", "no", "off"]
defp to_s(nil), do: ""
defp to_s(v) when is_binary(v), do: v
defp to_s(v), do: to_string(v)
defp to_i(nil, default), do: default
defp to_i(v, default) when is_integer(v), do: if(v > 0, do: v, else: default)
defp to_i(v, default) when is_binary(v) do
case Integer.parse(v) do
{i, _} when i > 0 -> i
_ -> default
end
end
defp to_i(_, default), do: default
end