defmodule EcsElixirCore.Application.Features.Sampling.SamplingConfig do
@moduledoc """
Manages sampling feature configuration.
Reads sampling rules from application config, parses them into a
`SamplingRuleSet` and caches the result in `:persistent_term` to avoid
re-parsing on every request.
This module belongs to the application layer because the application
layer is responsible for managing configuration for both the middleware
and its features.
"""
alias EcsElixirCore.Domain.Model.Features.Sampling.Model.SamplingRuleSet
alias EcsElixirCore.Domain.Shared.InternalLogging
@cache_key {__MODULE__, :ruleset_cache}
@doc "Returns the current sampling ruleset, loading and caching it on first access."
@spec fetch_ruleset() :: {:ok, SamplingRuleSet.t()} | {:error, term()}
def fetch_ruleset do
source_app = Application.get_env(:ecs_elixir_core, :sampling_source_app, :ecs_elixir_core)
source_key = Application.get_env(:ecs_elixir_core, :sampling_source_key, :ecs_sampling)
config = Application.get_env(source_app, source_key, [])
rules20x_json = get_config_value(config, :rules20XJson)
rules40x_json = get_config_value(config, :rules40XJson)
fingerprint = {source_app, source_key, rules20x_json, rules40x_json}
case :persistent_term.get(@cache_key, :empty) do
{^fingerprint, result} ->
result
_ ->
result = parse_ruleset(rules20x_json, rules40x_json)
log_rules_loaded(result, source_app, source_key)
:persistent_term.put(@cache_key, {fingerprint, result})
result
end
end
@doc "Clears the cached ruleset, forcing a reload on the next call."
@spec clear_cache() :: :ok
def clear_cache do
:persistent_term.erase(@cache_key)
:ok
end
defp parse_ruleset(rules20x_json, rules40x_json) do
with {:ok, rules20x} <- SamplingRuleSet.parse_rules(rules20x_json, :rules20x),
{:ok, rules40x} <- SamplingRuleSet.parse_rules(rules40x_json, :rules40x),
{:ok, ruleset} <- SamplingRuleSet.build_ruleset(rules20x, rules40x) do
{:ok, ruleset}
else
{:error, _reason} = error -> error
end
end
defp get_config_value(config, key) when is_map(config), do: Map.get(config, key)
defp get_config_value(config, key) when is_list(config), do: Keyword.get(config, key)
defp get_config_value(_config, _key), do: nil
defp log_rules_loaded({:ok, %SamplingRuleSet{} = ruleset}, source_app, source_key) do
rules20x_count = map_size(ruleset.rules20x)
rules40x_count = map_size(ruleset.rules40x)
total_rules = rules20x_count + rules40x_count
InternalLogging.log_debug(
"Sampling rules loaded from #{inspect(source_app)}:#{inspect(source_key)} - " <>
"20X=#{rules20x_count}, 40X=#{rules40x_count}, total=#{total_rules}",
__MODULE__
)
end
defp log_rules_loaded({:error, _reason}, _source_app, _source_key), do: :ok
end