Skip to content

Commit 51d7e48

Browse files
committed
Initial work on managed variables
1 parent f08688f commit 51d7e48

File tree

11 files changed

+764
-2
lines changed

11 files changed

+764
-2
lines changed

logfire/_internal/config.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767

6868
from ..propagate import NoExtractTraceContextPropagator, WarnOnExtractTraceContextPropagator
6969
from ..types import ExceptionCallback
70+
from ..variables.config import VariablesConfig
71+
from ..variables.providers.abstract import NoOpVariableProvider, VariableProvider
72+
from ..variables.providers.local import LogfireLocalProvider
7073
from .client import InvalidProjectName, LogfireClient, ProjectAlreadyExists
7174
from .config_params import ParamManager, PydanticPluginRecordValues
7275
from .constants import (
@@ -258,6 +261,44 @@ class CodeSource:
258261
"""
259262

260263

264+
@dataclass(init=False)
265+
class VariablesOptions:
266+
"""Configuration of managed variables."""
267+
268+
provider: VariableProvider
269+
include_resource_attributes_in_context: bool = True
270+
include_baggage_in_context: bool = True
271+
272+
def __init__(
273+
self,
274+
provider: VariableProvider | VariablesConfig | None = None,
275+
include_resource_attributes_in_context: bool = True,
276+
include_baggage_in_context: bool = True,
277+
):
278+
if isinstance(provider, VariablesConfig):
279+
provider = LogfireLocalProvider(provider)
280+
elif not provider:
281+
provider = NoOpVariableProvider()
282+
283+
self.provider = provider
284+
self.include_resource_attributes_in_context = include_resource_attributes_in_context
285+
self.include_baggage_in_context = include_baggage_in_context
286+
287+
# """Provider for resolving values of Variables.
288+
#
289+
# Defaults to setting an OFREP-compatible provider that hits Logfire's API."""
290+
#
291+
# create_remote_spans: bool = True
292+
# """Whether to insert OTel spans for variable resolution when using the Logfire OFREP provider.
293+
#
294+
# Has no impact if using a non-Logfire provider."""
295+
#
296+
# create_local_spans: bool | None = None
297+
# """Whether to open OTel spans for variable resolution in the process.
298+
#
299+
# Defaults to True if using a non-Logfire provider, otherwise False."""
300+
301+
261302
class DeprecatedKwargs(TypedDict):
262303
# Empty so that passing any additional kwargs makes static type checkers complain.
263304
pass
@@ -282,6 +323,7 @@ def configure( # noqa: D417
282323
min_level: int | LevelName | None = None,
283324
add_baggage_to_attributes: bool = True,
284325
code_source: CodeSource | None = None,
326+
variables: VariablesOptions | None = None,
285327
distributed_tracing: bool | None = None,
286328
advanced: AdvancedOptions | None = None,
287329
**deprecated_kwargs: Unpack[DeprecatedKwargs],
@@ -346,6 +388,7 @@ def configure( # noqa: D417
346388
add_baggage_to_attributes: Set to `False` to prevent OpenTelemetry Baggage from being added to spans as attributes.
347389
See the [Baggage documentation](https://logfire.pydantic.dev/docs/reference/advanced/baggage/) for more details.
348390
code_source: Settings for the source code of the project.
391+
variables: Options related to managed variables.
349392
distributed_tracing: By default, incoming trace context is extracted, but generates a warning.
350393
Set to `True` to disable the warning.
351394
Set to `False` to suppress extraction of incoming trace context.
@@ -482,6 +525,7 @@ def configure( # noqa: D417
482525
sampling=sampling,
483526
add_baggage_to_attributes=add_baggage_to_attributes,
484527
code_source=code_source,
528+
variables=variables,
485529
distributed_tracing=distributed_tracing,
486530
advanced=advanced,
487531
)
@@ -546,6 +590,9 @@ class _LogfireConfigData:
546590
code_source: CodeSource | None
547591
"""Settings for the source code of the project."""
548592

593+
variables: VariablesOptions
594+
"""Settings related to managed variables."""
595+
549596
distributed_tracing: bool | None
550597
"""Whether to extract incoming trace context."""
551598

@@ -573,6 +620,7 @@ def _load_configuration(
573620
min_level: int | LevelName | None,
574621
add_baggage_to_attributes: bool,
575622
code_source: CodeSource | None,
623+
variables: VariablesOptions | None,
576624
distributed_tracing: bool | None,
577625
advanced: AdvancedOptions | None,
578626
) -> None:
@@ -639,6 +687,8 @@ def _load_configuration(
639687
code_source = CodeSource(**code_source) # type: ignore
640688
self.code_source = code_source
641689

690+
self.variables = variables or VariablesOptions()
691+
642692
if isinstance(advanced, dict):
643693
# This is particularly for deserializing from a dict as in executors.py
644694
advanced = AdvancedOptions(**advanced) # type: ignore
@@ -682,6 +732,7 @@ def __init__(
682732
sampling: SamplingOptions | None = None,
683733
min_level: int | LevelName | None = None,
684734
add_baggage_to_attributes: bool = True,
735+
variables: VariablesOptions | None = None,
685736
code_source: CodeSource | None = None,
686737
distributed_tracing: bool | None = None,
687738
advanced: AdvancedOptions | None = None,
@@ -711,6 +762,7 @@ def __init__(
711762
min_level=min_level,
712763
add_baggage_to_attributes=add_baggage_to_attributes,
713764
code_source=code_source,
765+
variables=variables,
714766
distributed_tracing=distributed_tracing,
715767
advanced=advanced,
716768
)
@@ -720,6 +772,7 @@ def __init__(
720772
# note: this reference is important because the MeterProvider runs things in background threads
721773
# thus it "shuts down" when it's gc'ed
722774
self._meter_provider = ProxyMeterProvider(NoOpMeterProvider())
775+
self._variable_provider = NoOpVariableProvider()
723776
self._logger_provider = ProxyLoggerProvider(NoOpLoggerProvider())
724777
try:
725778
from opentelemetry.sdk._events import EventLoggerProvider as SDKEventLoggerProvider
@@ -750,6 +803,7 @@ def configure(
750803
min_level: int | LevelName | None,
751804
add_baggage_to_attributes: bool,
752805
code_source: CodeSource | None,
806+
variables: VariablesOptions | None,
753807
distributed_tracing: bool | None,
754808
advanced: AdvancedOptions | None,
755809
) -> None:
@@ -772,6 +826,7 @@ def configure(
772826
min_level,
773827
add_baggage_to_attributes,
774828
code_source,
829+
variables,
775830
distributed_tracing,
776831
advanced,
777832
)
@@ -1103,6 +1158,9 @@ def fix_pid(): # pragma: no cover
11031158
) # note: this may raise an Exception if it times out, call `logfire.shutdown` first
11041159
self._meter_provider.set_meter_provider(meter_provider)
11051160

1161+
self._variable_provider.shutdown()
1162+
self._variable_provider = self.variables.provider
1163+
11061164
multi_log_processor = SynchronousMultiLogRecordProcessor()
11071165
for processor in log_record_processors:
11081166
multi_log_processor.add_log_record_processor(processor)

logfire/_internal/main.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import json
66
import sys
77
import warnings
8-
from collections.abc import Iterable, Sequence
8+
from collections.abc import Iterable, Mapping, Sequence
99
from contextlib import AbstractContextManager
1010
from contextvars import Token
1111
from enum import Enum
@@ -30,6 +30,7 @@
3030
from opentelemetry.util import types as otel_types
3131
from typing_extensions import LiteralString, ParamSpec
3232

33+
from ..variables.variable import ResolveFunction, Variable
3334
from ..version import VERSION
3435
from . import async_
3536
from .auto_trace import AutoTraceModule, install_auto_tracing
@@ -125,6 +126,8 @@
125126
# 3. The argument name exc_info is very suggestive of the sys function.
126127
ExcInfo = Union[SysExcInfo, BaseException, bool, None]
127128

129+
T = TypeVar('T')
130+
128131

129132
class Logfire:
130133
"""The main logfire class."""
@@ -148,6 +151,10 @@ def __init__(
148151
def config(self) -> LogfireConfig:
149152
return self._config
150153

154+
@property
155+
def resource_attributes(self) -> Mapping[str, Any]:
156+
return self._tracer_provider.resource.attributes
157+
151158
@cached_property
152159
def _tracer_provider(self) -> ProxyTracerProvider:
153160
self._config.warn_if_not_initialized('No logs or spans will be created')
@@ -2329,8 +2336,23 @@ def shutdown(self, timeout_millis: int = 30_000, flush: bool = True) -> bool: #
23292336
if not remaining: # pragma: no cover
23302337
return False
23312338
self._meter_provider.shutdown(remaining)
2339+
2340+
remaining = max(0, timeout_millis - (time() - start))
2341+
if not remaining: # pragma: no cover
2342+
return False
2343+
2344+
self.config.variables.provider.shutdown()
2345+
23322346
return (start - time()) < timeout_millis
23332347

2348+
def var(self, *, name: str, default: T | ResolveFunction[T], type: type[T] | Sequence[type[T]]) -> Variable[T]:
2349+
tp: type[T]
2350+
if isinstance(type, Sequence):
2351+
tp = Union[tuple(type)] # pyright: ignore[reportAssignmentType]
2352+
else:
2353+
tp = type
2354+
return Variable[T](name, default=default, type=tp, logfire_instance=self)
2355+
23342356

23352357
class FastLogfireSpan:
23362358
"""A simple version of `LogfireSpan` optimized for auto-tracing."""

logfire/variables/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from importlib.util import find_spec
2+
3+
if not find_spec('pydantic'):
4+
raise RuntimeError(
5+
'The `logfire.variables` module requires the `pydantic` package.\n'
6+
'You can install this with:\n'
7+
" pip install 'logfire[variables]'"
8+
)

0 commit comments

Comments
 (0)