ForgeOpsTracker.Configuration (forge_ops_tracker v0.15.0)

Copy Markdown View Source

Holds a single ForgeOps DSN plus everything else the client needs to build and deliver events. Mirrors gems/forge_ops_tracker's Configuration: a single DSN string carries both the ingestion URL and the project's api_key: https://<api_key>@host/api/v1/events.

Backed by a GenServer (started under ForgeOpsTracker.Application's supervision tree, the same "shared mutable state" role every other SDK in this repo's own Configuration class plays) rather than a plain struct a caller has to thread through every call: Elixir has no language-level singleton the way Ruby's module-level state or a Kotlin object gives other clients in this repo for free, so a supervised process is the idiomatic BEAM equivalent.

Summary

Functions

The DSN's userinfo component, percent-decoded: nil if the DSN is unset or malformed.

Same derivation again, swapping the trailing /events for /change_snapshots.

Same derivation again, swapping the trailing /events for /changes.

Returns a specification to start this module under a supervisor.

Same derivation as performance_samples_uri/1, swapping the trailing /events for /custom_metrics.

The one-line warning ForgeOpsTracker.init/1 logs when a DSN is configured but the environment isn't one of enabled_environments, so nothing would be sent; nil when there's nothing to warn about (no DSN, or the environment is enabled).

The current configuration.

Same derivation again, swapping the trailing /events for /infrastructure_metrics.

The ingestion URL with credentials stripped out (they travel as the Authorization header instead).

Same host and api_key as ingestion_uri/1, a sibling path under the same DSN rather than a second DSN to configure: the DSN's own path is always /api/v1/events (what ForgeOps issues to every project), so the performance-samples endpoint is derived by substitution, the same approach gems/forge_ops_tracker's own Configuration#performance_samples_uri already takes.

Whether an outgoing request to host should carry a traceparent header; see propagate_traces/trace_propagation_targets above. Case-insensitive, since hostnames are.

Same derivation again, swapping the trailing /events for /query_plans.

The environment the client starts with, given the OS environment variables (System.get_env/0), the app's config :forge_ops_tracker, environment: value, and Mix.env/0 when this VM is running under Mix (nil otherwise), in this order

Same derivation as performance_samples_uri/1, swapping the trailing /events for /spans.

Updates the configuration via a function that receives the current struct and returns the new one (ForgeOpsTracker.Configuration.update(&%{&1 | dsn: "..."})) the closest Elixir equivalent to every other SDK's own mutable Configuration object, without actually mutating anything (this replaces the GenServer's state, doesn't mutate a shared struct in place).

Types

t()

@type t() :: %ForgeOpsTracker.Configuration{
  app_module_prefix: String.t() | nil,
  capture_source_context: boolean(),
  capture_sql_objects: boolean(),
  capture_sql_statement: boolean(),
  detect_changes: boolean(),
  dsn: String.t() | nil,
  enabled_environments: MapSet.t(String.t()),
  environment: String.t(),
  explain_slow_queries: boolean(),
  explain_threshold_ms: non_neg_integer(),
  infrastructure_metric_flush_interval: pos_integer(),
  logger: (String.t() -> any()) | nil,
  max_breadcrumbs: pos_integer(),
  metric_flush_interval: pos_integer(),
  performance_flush_interval: pos_integer(),
  propagate_traces: boolean(),
  queue_size: pos_integer(),
  release: String.t() | nil,
  scrub_pii: boolean(),
  server_name: String.t() | nil,
  timeout: pos_integer(),
  trace_capture_threshold: non_neg_integer(),
  trace_propagation_targets: [String.t() | Regex.t()] | nil,
  track_breadcrumbs: boolean(),
  track_env_var_names: boolean(),
  track_performance: boolean(),
  track_tracing: boolean()
}

Functions

api_key(config)

@spec api_key(t()) :: String.t() | nil

The DSN's userinfo component, percent-decoded: nil if the DSN is unset or malformed.

change_snapshots_uri(config)

@spec change_snapshots_uri(t()) :: String.t() | nil

Same derivation again, swapping the trailing /events for /change_snapshots.

changes_uri(config)

@spec changes_uri(t()) :: String.t() | nil

Same derivation again, swapping the trailing /events for /changes.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

custom_metrics_uri(config)

@spec custom_metrics_uri(t()) :: String.t() | nil

Same derivation as performance_samples_uri/1, swapping the trailing /events for /custom_metrics.

disabled_environment_warning(config)

@spec disabled_environment_warning(t()) :: String.t() | nil

The one-line warning ForgeOpsTracker.init/1 logs when a DSN is configured but the environment isn't one of enabled_environments, so nothing would be sent; nil when there's nothing to warn about (no DSN, or the environment is enabled).

enabled?(config)

@spec enabled?(t()) :: boolean()

get()

@spec get() :: t()

The current configuration.

infrastructure_metrics_uri(config)

@spec infrastructure_metrics_uri(t()) :: String.t() | nil

Same derivation again, swapping the trailing /events for /infrastructure_metrics.

ingestion_uri(config)

@spec ingestion_uri(t()) :: String.t() | nil

The ingestion URL with credentials stripped out (they travel as the Authorization header instead).

log(configuration, message)

@spec log(t(), String.t()) :: :ok

performance_samples_uri(config)

@spec performance_samples_uri(t()) :: String.t() | nil

Same host and api_key as ingestion_uri/1, a sibling path under the same DSN rather than a second DSN to configure: the DSN's own path is always /api/v1/events (what ForgeOps issues to every project), so the performance-samples endpoint is derived by substitution, the same approach gems/forge_ops_tracker's own Configuration#performance_samples_uri already takes.

propagate_trace?(configuration, host)

@spec propagate_trace?(t(), String.t() | nil) :: boolean()

Whether an outgoing request to host should carry a traceparent header; see propagate_traces/trace_propagation_targets above. Case-insensitive, since hostnames are.

query_plans_uri(config)

@spec query_plans_uri(t()) :: String.t() | nil

Same derivation again, swapping the trailing /events for /query_plans.

resolve_environment(os_env, app_environment, mix_env)

@spec resolve_environment(%{optional(String.t()) => String.t()}, term(), atom() | nil) ::
  String.t()

The environment the client starts with, given the OS environment variables (System.get_env/0), the app's config :forge_ops_tracker, environment: value, and Mix.env/0 when this VM is running under Mix (nil otherwise), in this order:

  1. FORGE_OPS_ENVIRONMENT
  2. config :forge_ops_tracker, environment: ...
  3. MIX_ENV, with prod read as "production", dev as "development" and test as "test" (any other value as written)
  4. Mix.env/0, mapped the same way: mix doesn't put its default :dev into MIX_ENV, so this is what keeps iex -S mix or mix phx.server on a developer's machine from reporting as production
  5. "production": a Mix release, an escript or an elixir script, where Mix isn't running and nothing above is set, so an app deployed with nothing but a DSN still sends its errors.

An empty value counts as unset.

spans_uri(config)

@spec spans_uri(t()) :: String.t() | nil

Same derivation as performance_samples_uri/1, swapping the trailing /events for /spans.

update(fun)

@spec update((t() -> t())) :: t()

Updates the configuration via a function that receives the current struct and returns the new one (ForgeOpsTracker.Configuration.update(&%{&1 | dsn: "..."})) the closest Elixir equivalent to every other SDK's own mutable Configuration object, without actually mutating anything (this replaces the GenServer's state, doesn't mutate a shared struct in place).

If fun raises, that's re-raised here, in the calling process, with its original stacktrace: not left to crash this shared, process-wide GenServer (which every other part of a host app calling ForgeOpsTracker also depends on) over what's usually just a caller bug, like an unknown configuration key.