Unleash. Propagation
(Unleash v5.1.2)
View Source
The Unleash Propagation mechanism enables the propagation of feature flag contexts, overrides, and impressions throughout a distributed system, similarly to how OpenTelemetry helps propagate Trace Contexts to build distributed traces.
Downstream Propagation (Context and Overrides)
Downstream propagation allows you to set unleash context and overrides which will automatically
apply to any subsequent calls to Unleash.enabled?/2 and Unleash.get_variant/2 calls
within the same process or its descendants.
put_context/1: Sets a context which will be used to enrich the local context when evaluating activation strategies of a feature toggle. The local context has precendence and its values will never be overwritten by the propagated context.put_overrides/1: Sets feature flags overrides that take precedence over normal feature evaluations.
For Plug-based applications (such as Phoenix apps), you can use the Plugs provided in
Unleash.Propagation.Plugs to automatically extract context and overrides from request headers.
To further propagate them when calling external downstream services, you can retrieve them
using get_context/0 and get_overrides/0, and use the Unleash.Propagation.Serialization module
to serialize them to request headers.
Upstream Propagation (Impressions)
This mechanism captures data on which feature flags are being checked during a request by the current process and its descendants. This information can be returned to the caller (e.g., included in response headers).
track_impressions/0: Begins tracking feature flag checks for the current process and descendants, returning a reference to the tracking session.impressions/0: Retrieves the list of tracked impressions. The impressions are deduplicated based on (feature_name, status); meaning you could have up to 2 impressions on the same feature if it has been queried twice with different results (which should be quite rare).
Impression tracking has limited support for variants: impressions are recorded when a feature is queried
via get_variant/3, but only the enabled: boolean() status will be recorded, without any information
on the returned variant.
Scoping of the Propagation Mechanism
Both the context and impressions tracking mechanisms are scoped to the process tree branch, ensuring data is confined to the current process and its descendants. This helps manage and contain the state within logical execution boundaries, like a request lifecycle. The SDK uses ProcessTree to achieve this, so its limitations regarding tracking of parent processes apply.
For global, unscoped, feature flag impressions tracking, you should not use the impressions features provided by this module, but you should instead hook into the telemetry events emitted by the SDK:
[:unleash, :feature, :enabled][:unleash, :variant, :get]
Degraded propagation lookups
When a propagation lookup's ancestor walk fails (e.g. ProcessTree.get/1 raising
ArgumentError because the process ancestry contains a remote pid), the lookup
degrades gracefully to "no propagated value" so it never takes down core flag
evaluation.
Because a process's ancestry is fixed for its lifetime, a failing walk fails for
every subsequent lookup in that process — and each flag evaluation performs
several lookups (context, overrides, impressions). Rather than logging on this
hot path (which would flood the log), a [:unleash, :propagation, :degraded]
telemetry event is emitted on every degradation, giving operators an aggregate
counter to detect persistent degradation. The event metadata carries the pdict
key and the raised error's message (never a propagated value — there isn't one,
the walk failed).
Summary
Types
Opaque reference to an impressions tracking session.
Functions
Returns the context propagated to the current process, or nil if none was set.
Returns the overrides propagated to the current process, or nil if none were set.
Merges externally-supplied impressions into the current tracking session, if any is active.
Returns the impressions generated within this tracking session,
i.e. since track_impressions/0 was called by the current process or one of its ancestors.
Sets the context propagated to the current process and its descendants. See moduledoc.
Sets the overrides propagated to the current process and its descendants. See moduledoc.
Records an impression in the current tracking session. Does nothing if there's no active tracking session.
Starts tracking impressions generated by the current process and descendant processes.
Types
Functions
@spec get_context() :: Unleash.context() | nil
Returns the context propagated to the current process, or nil if none was set.
@spec get_overrides() :: Unleash.overrides() | nil
Returns the overrides propagated to the current process, or nil if none were set.
@spec import_impressions([Unleash.impression()]) :: :ok
Merges externally-supplied impressions into the current tracking session, if any is active.
@spec impressions() :: [Unleash.impression()] | nil
Returns the impressions generated within this tracking session,
i.e. since track_impressions/0 was called by the current process or one of its ancestors.
Returns nil if no impression tracking session is active.
See track_impressions/0 for more details.
@spec put_context(Unleash.context()) :: :ok
Sets the context propagated to the current process and its descendants. See moduledoc.
@spec put_overrides(Unleash.overrides()) :: :ok
Sets the overrides propagated to the current process and its descendants. See moduledoc.
Records an impression in the current tracking session. Does nothing if there's no active tracking session.
This function is used by Unleash, typically you should not call this directly.
See track_impressions/0 for more details.
@spec track_impressions() :: {:ok, tracking_session()} | {:error, {:already_tracking, tracking_session()}}
Starts tracking impressions generated by the current process and descendant processes.
Returns {:ok, ref} where ref is an opaque reference to this specific tracking session,
or {:error, {:already_tracking, ref}} if there was a preexisting tracking session for
the current process.
The recorded impressions can then be retrieved by calling impressions/0.
See Unleash.impression/0 for a brief introduction to Unleash impressions.
Note that to prevent unbounded memory usage, this function should be called from a process with a limited lifetime, e.g. a request handler. All the recorded impressions are scoped to the calling process, and they are deleted and garbage-collected when the process dies.