Managed Variables
Relevant source files
The following files were used as context for generating this wiki page:
Managed Variables in Pydantic Logfire provide a robust system for handling application configurations, feature flags, and A/B testing parameters that can be updated dynamically. These variables are defined in code with default values but can be overridden by a central configuration provider (either remote via the Logfire API or local for testing) without requiring application restarts.
The system supports complex targeting rules based on user attributes, environment data, or OpenTelemetry baggage. It also facilitates deterministic rollouts using targeting keys, ensuring that specific users receive consistent values during A/B tests or phased feature releases.
Core Architecture
The Managed Variables system is built around a provider-based architecture where the application interacts with a high-level Variable object, which in turn consults a VariableProvider to resolve the current value.
The architecture decouples the definition of a variable from its source of truth, allowing developers to swap between local in-memory configurations and remote API-driven management.
Sources: logfire/variables/abstract.py:580-585, logfire/variables/variable.py:120-130
Key Components
| Component | Description |
|---|---|
Variable | The primary handle used in application code to register and retrieve variable values. |
VariableProvider | Abstract base class defining the interface for fetching serialized variable values and configurations. |
VariablesConfig | A Pydantic-based container that holds the definitions, labels, and rollout rules for all managed variables. |
VariableConfig | The configuration for a specific variable, including its targeting overrides and rollout weights. |
ResolvedVariable | A container returned upon resolution that includes the value, metadata (label, version), and any execution context. |
Sources: logfire/variables/config.py:315-325, logfire/variables/abstract.py:58-65
Variable Resolution Logic
When an application calls variable.get(), the system performs a multi-step resolution process. It combines local defaults with provider-supplied configurations and contextual attributes to determine the final value.
Resolution Workflow
- Context Gathering: The system collects attributes from OpenTelemetry baggage, resource attributes (if enabled), and the explicit
targeting_context. - Targeting Key Selection: It identifies a targeting key, prioritizing explicit arguments, then trace IDs (if in an active span), and finally context variables.
- Override Evaluation: The
VariableConfigevaluatesoverridesin order. The first override whose conditions match the attributes is selected. - Label Selection: Within the selected rollout, a label is chosen based on weighted probabilities. If a targeting key is present, selection is deterministic.
- Label Reference Following: If the selected label is a reference (e.g., to 'latest' or another label), the system follows the chain to find the actual value.
- Fallback: If no configuration exists or no label is selected, the system falls back to the default value provided in code.
The resolution logic ensures that complex rules like "Enable feature X for 10% of users in the 'enterprise' plan" can be evaluated locally by the SDK.
Sources: logfire/variables/config.py:415-450, logfire/variables/config.py:465-490
Targeting and Conditions
Managed Variables support sophisticated targeting through RolloutOverride objects and Condition types. Conditions allow filtering based on attributes provided at runtime or extracted from the environment.
Supported Condition Types
| Kind | Description | Matches If... |
|---|---|---|
value-equals | Exact match check. | attr_value == target_value |
value-is-in | Membership check. | attr_value is in [val1, val2, ...] |
value-matches-regex | Regular expression. | regex.search(pattern, attr_value) is true. |
key-is-present | Existence check. | The attribute key exists in the context. |
value-does-not-equal | Inverse exact match. | attr_value != target_value |
Sources: logfire/variables/config.py:45-125
Remote Provider and SSE
The LogfireRemoteVariableProvider is the production-grade implementation that fetches configurations from the Logfire API. It uses a combination of polling and Server-Sent Events (SSE) for real-time updates.
Architecture of Remote Updates
The remote provider maintains a background worker thread for polling and a separate thread for handling SSE connections. When a change is made in the Logfire UI, an SSE event is dispatched, triggering the provider to immediately refresh its local cache.
By default, the provider polls every 60 seconds as a fallback, but SSE ensures that most updates are propagated to the application within milliseconds.
Sources: logfire/variables/remote.py:50-80, logfire/variables/remote.py:150-180
Configuration Options
The system can be configured during the logfire.configure() call using VariablesOptions (for remote) or LocalVariablesOptions (for local/testing).
VariablesOptions (Remote)
| Parameter | Type | Default | Description |
|---|---|---|---|
block_before_first_resolve | bool | True | Whether to wait for the first API fetch before resolving variables. |
polling_interval | timedelta | 60s | Fallback interval for checking configuration updates. |
include_baggage_in_context | bool | True | Automatically include OTel baggage in resolution attributes. |
instrument | bool | True | Create OpenTelemetry spans for every variable resolution. |
Sources: logfire/_internal/config.py:220-245
LocalVariablesOptions
| Parameter | Type | Description |
|---|---|---|
config | VariablesConfig | An in-memory Pydantic model containing all variable definitions. |
instrument | bool | Whether to create spans during resolution. |
Sources: logfire/_internal/config.py:255-265
Usage Example
Variables are typically registered at the module level and accessed where needed.
import logfire
# 1. Register the variable
feature_flag = logfire.var(
name="new_search_algorithm",
default=False,
type=bool,
description="Toggle for the experimental search backend"
)
# 2. Use the variable
def search(query: str, user_id: str):
# Deterministic resolution based on user_id
with feature_flag.get(targeting_key=user_id) as details:
if details.value:
return run_experimental_search(query)
return run_legacy_search(query)
When used as a context manager (with ...), the ResolvedVariable automatically sets OpenTelemetry baggage. This ensures that any logs or spans generated inside the block are tagged with the variable label (e.g., logfire.variables.new_search_algorithm = "v2_experiment"), enabling precise observability into how different variable values affect application performance or errors.
Sources: logfire/variables/abstract.py:100-120, logfire/variables/variable.py:150-170
Summary
Managed Variables bridge the gap between static configuration and dynamic control. By leveraging OpenTelemetry baggage and Pydantic validation, the system provides a type-safe, observable, and highly targeted way to manage application behavior in real-time. Whether used for simple feature toggles or complex multi-variant A/B tests, the architecture ensures consistency and visibility across distributed systems.