From 89a10a545b6c274909ff7f5d6adee9273871695b Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Tue, 15 Oct 2024 17:41:10 +0200 Subject: [PATCH 1/3] Add local parameter to logfire.configure --- logfire/_internal/config.py | 30 +++++++++++++------ tests/test_configure.py | 57 +++++++++++++++++++++++++++++++++++++ uv.lock | 6 ++-- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/logfire/_internal/config.py b/logfire/_internal/config.py index 3d8fdd024..d460d57b6 100644 --- a/logfire/_internal/config.py +++ b/logfire/_internal/config.py @@ -96,7 +96,7 @@ ) if TYPE_CHECKING: - from .main import FastLogfireSpan, LogfireSpan + from .main import FastLogfireSpan, Logfire, LogfireSpan # NOTE: this WeakSet is the reason that FastLogfireSpan.__slots__ has a __weakref__ slot. OPEN_SPANS: WeakSet[LogfireSpan | FastLogfireSpan] = WeakSet() @@ -223,6 +223,7 @@ class DeprecatedKwargs(TypedDict): def configure( # noqa: D417 *, + local: bool = False, send_to_logfire: bool | Literal['if-token-present'] | None = None, token: str | None = None, service_name: str | None = None, @@ -238,10 +239,12 @@ def configure( # noqa: D417 code_source: CodeSource | None = None, advanced: AdvancedOptions | None = None, **deprecated_kwargs: Unpack[DeprecatedKwargs], -) -> None: +) -> Logfire: """Configure the logfire SDK. Args: + local: If `True`, configures and returns a `Logfire` instance that is not the default global instance. + Use this to create multiple separate configurations, e.g. to send to different projects. send_to_logfire: Whether to send logs to logfire.dev. Defaults to the `LOGFIRE_SEND_TO_LOGFIRE` environment variable if set, otherwise defaults to `True`. If `if-token-present` is provided, logs will only be sent if a token is present. @@ -269,6 +272,8 @@ def configure( # noqa: D417 This setting is experimental, and may change in the future! advanced: Advanced options primarily used for testing by Logfire developers. """ + from .. import DEFAULT_LOGFIRE_INSTANCE, Logfire + processors = deprecated_kwargs.pop('processors', None) # type: ignore if processors is not None: # pragma: no cover raise ValueError( @@ -376,7 +381,11 @@ def configure( # noqa: D417 if deprecated_kwargs: raise TypeError(f'configure() got unexpected keyword arguments: {", ".join(deprecated_kwargs)}') - GLOBAL_CONFIG.configure( + if local: + config = LogfireConfig() + else: + config = GLOBAL_CONFIG + config.configure( send_to_logfire=send_to_logfire, token=token, service_name=service_name, @@ -393,6 +402,11 @@ def configure( # noqa: D417 advanced=advanced, ) + if local: + return Logfire(config=config) + else: + return DEFAULT_LOGFIRE_INSTANCE + def _get_int_from_env(env_var: str) -> int | None: value = os.getenv(env_var) @@ -638,14 +652,14 @@ def configure( ) self.initialize() - def initialize(self) -> ProxyTracerProvider: + def initialize(self) -> None: """Configure internals to start exporting traces and metrics.""" with self._lock: - return self._initialize() + self._initialize() - def _initialize(self) -> ProxyTracerProvider: + def _initialize(self) -> None: if self._initialized: # pragma: no cover - return self._tracer_provider + return with suppress_instrumentation(): otel_resource_attributes: dict[str, Any] = { @@ -867,8 +881,6 @@ def _exit_open_spans(): # type: ignore[reportUnusedFunction] # pragma: no cove self._ensure_flush_after_aws_lambda() - return self._tracer_provider - def force_flush(self, timeout_millis: int = 30_000) -> bool: """Force flush all spans and metrics. diff --git a/tests/test_configure.py b/tests/test_configure.py index 63c176880..08c5ced20 100644 --- a/tests/test_configure.py +++ b/tests/test_configure.py @@ -1650,3 +1650,60 @@ def test_code_source(config_kwargs: dict[str, Any], exporter: TestExporter): } ] ) + + +def test_local_config(exporter: TestExporter, config_kwargs: dict[str, Any]): + local_exporter = TestExporter() + config_kwargs['additional_span_processors'] = [SimpleSpanProcessor(local_exporter)] + local_logfire = logfire.configure(**config_kwargs, local=True) + + assert local_logfire != logfire.DEFAULT_LOGFIRE_INSTANCE + assert local_logfire.config != logfire.DEFAULT_LOGFIRE_INSTANCE.config + + logfire.info('test1') + local_logfire.info('test2') + + assert exporter.exported_spans_as_dict() == snapshot( + [ + { + 'name': 'test1', + 'context': {'trace_id': 1, 'span_id': 1, 'is_remote': False}, + 'parent': None, + 'start_time': 1000000000, + 'end_time': 1000000000, + 'attributes': { + 'logfire.span_type': 'log', + 'logfire.level_num': 9, + 'logfire.msg_template': 'test1', + 'logfire.msg': 'test1', + 'code.filepath': 'test_configure.py', + 'code.function': 'test_local_config', + 'code.lineno': 123, + }, + } + ] + ) + assert local_exporter.exported_spans_as_dict() == snapshot( + [ + { + 'name': 'test2', + 'context': { + 'trace_id': 2, + 'span_id': 2, + 'is_remote': False, + }, + 'parent': None, + 'start_time': 2000000000, + 'end_time': 2000000000, + 'attributes': { + 'logfire.span_type': 'log', + 'logfire.level_num': 9, + 'logfire.msg_template': 'test2', + 'logfire.msg': 'test2', + 'code.filepath': 'test_configure.py', + 'code.function': 'test_local_config', + 'code.lineno': 123, + }, + } + ] + ) diff --git a/uv.lock b/uv.lock index ef451bb0e..f0a64903f 100644 --- a/uv.lock +++ b/uv.lock @@ -431,7 +431,7 @@ name = "cffi" version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "pycparser", marker = "platform_python_implementation != 'PyPy' or python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } wheels = [ @@ -1365,7 +1365,7 @@ wheels = [ [[package]] name = "logfire" -version = "1.0.1" +version = "1.1.0" source = { editable = "." } dependencies = [ { name = "executing" }, @@ -1612,7 +1612,7 @@ dev = [ [[package]] name = "logfire-api" -version = "1.0.1" +version = "1.1.0" source = { editable = "logfire-api" } [[package]] From 99cc3181dafe62ab9015c8dc26c6ac3864e63868 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Tue, 15 Oct 2024 18:12:41 +0200 Subject: [PATCH 2/3] pass providers to instrumentations --- logfire/_internal/config.py | 2 -- .../_internal/integrations/aiohttp_client.py | 12 ++++++-- logfire/_internal/integrations/asyncpg.py | 12 ++++++-- logfire/_internal/integrations/celery.py | 12 ++++++-- logfire/_internal/integrations/django.py | 11 +++++-- logfire/_internal/integrations/fastapi.py | 6 +++- logfire/_internal/integrations/flask.py | 14 +++++++-- logfire/_internal/integrations/httpx.py | 12 ++++++-- logfire/_internal/integrations/psycopg.py | 29 ++++++++++++++----- logfire/_internal/integrations/requests.py | 13 +++++++-- logfire/_internal/integrations/starlette.py | 7 +++-- logfire/_internal/main.py | 17 ++++++----- 12 files changed, 113 insertions(+), 34 deletions(-) diff --git a/logfire/_internal/config.py b/logfire/_internal/config.py index d460d57b6..6723f34af 100644 --- a/logfire/_internal/config.py +++ b/logfire/_internal/config.py @@ -901,7 +901,6 @@ def get_tracer_provider(self) -> ProxyTracerProvider: Returns: The tracer provider. """ - self.warn_if_not_initialized('No logs or spans will be created') return self._tracer_provider def get_meter_provider(self) -> ProxyMeterProvider: @@ -912,7 +911,6 @@ def get_meter_provider(self) -> ProxyMeterProvider: Returns: The meter provider. """ - self.warn_if_not_initialized('No metrics will be created') return self._meter_provider def warn_if_not_initialized(self, message: str): diff --git a/logfire/_internal/integrations/aiohttp_client.py b/logfire/_internal/integrations/aiohttp_client.py index b010476ac..44781932d 100644 --- a/logfire/_internal/integrations/aiohttp_client.py +++ b/logfire/_internal/integrations/aiohttp_client.py @@ -2,10 +2,18 @@ from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor +from logfire import Logfire -def instrument_aiohttp_client(**kwargs: Any): + +def instrument_aiohttp_client(logfire_instance: Logfire, **kwargs: Any): """Instrument the `aiohttp` module so that spans are automatically created for each client request. See the `Logfire.instrument_aiohttp_client` method for details. """ - AioHttpClientInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType] + AioHttpClientInstrumentor().instrument( # type: ignore[reportUnknownMemberType] + **{ + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + }, + ) diff --git a/logfire/_internal/integrations/asyncpg.py b/logfire/_internal/integrations/asyncpg.py index 879f8fdf5..a3f0e2766 100644 --- a/logfire/_internal/integrations/asyncpg.py +++ b/logfire/_internal/integrations/asyncpg.py @@ -4,6 +4,8 @@ from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor +from logfire import Logfire + if TYPE_CHECKING: from typing_extensions import TypedDict, Unpack @@ -11,9 +13,15 @@ class AsyncPGInstrumentKwargs(TypedDict, total=False): skip_dep_check: bool -def instrument_asyncpg(**kwargs: Unpack[AsyncPGInstrumentKwargs]) -> None: +def instrument_asyncpg(logfire_instance: Logfire, **kwargs: Unpack[AsyncPGInstrumentKwargs]) -> None: """Instrument the `asyncpg` module so that spans are automatically created for each query. See the `Logfire.instrument_asyncpg` method for details. """ - AsyncPGInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType] + AsyncPGInstrumentor().instrument( # type: ignore[reportUnknownMemberType] + **{ + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + } + ) diff --git a/logfire/_internal/integrations/celery.py b/logfire/_internal/integrations/celery.py index b07e1430a..3c677f0db 100644 --- a/logfire/_internal/integrations/celery.py +++ b/logfire/_internal/integrations/celery.py @@ -4,6 +4,8 @@ from opentelemetry.instrumentation.celery import CeleryInstrumentor +from logfire import Logfire + if TYPE_CHECKING: from typing_extensions import TypedDict, Unpack @@ -11,9 +13,15 @@ class CeleryInstrumentKwargs(TypedDict, total=False): skip_dep_check: bool -def instrument_celery(**kwargs: Unpack[CeleryInstrumentKwargs]) -> None: +def instrument_celery(logfire_instance: Logfire, **kwargs: Unpack[CeleryInstrumentKwargs]) -> None: """Instrument the `celery` module so that spans are automatically created for each task. See the `Logfire.instrument_celery` method for details. """ - return CeleryInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType] + return CeleryInstrumentor().instrument( # type: ignore[reportUnknownMemberType] + **{ + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + } + ) diff --git a/logfire/_internal/integrations/django.py b/logfire/_internal/integrations/django.py index 45beb67bb..952247639 100644 --- a/logfire/_internal/integrations/django.py +++ b/logfire/_internal/integrations/django.py @@ -1,5 +1,6 @@ from typing import Any +from logfire import Logfire from logfire._internal.utils import maybe_capture_server_headers try: @@ -12,10 +13,16 @@ ) -def instrument_django(*, capture_headers: bool = False, **kwargs: Any): +def instrument_django(logfire_instance: Logfire, *, capture_headers: bool = False, **kwargs: Any): """Instrument the `django` module so that spans are automatically created for each web request. See the `Logfire.instrument_django` method for details. """ maybe_capture_server_headers(capture_headers) - DjangoInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType] + DjangoInstrumentor().instrument( # type: ignore[reportUnknownMemberType] + **{ + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + } + ) diff --git a/logfire/_internal/integrations/fastapi.py b/logfire/_internal/integrations/fastapi.py index 00c5325ad..f7bf46dad 100644 --- a/logfire/_internal/integrations/fastapi.py +++ b/logfire/_internal/integrations/fastapi.py @@ -74,10 +74,14 @@ def instrument_fastapi( if use_opentelemetry_instrumentation: # pragma: no branch maybe_capture_server_headers(capture_headers) + opentelemetry_kwargs = { + 'tracer_provider': tweak_asgi_spans_tracer_provider(logfire_instance, record_send_receive), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **opentelemetry_kwargs, + } FastAPIInstrumentor.instrument_app( # type: ignore app, excluded_urls=excluded_urls, - tracer_provider=tweak_asgi_spans_tracer_provider(logfire_instance, record_send_receive), **opentelemetry_kwargs, ) diff --git a/logfire/_internal/integrations/flask.py b/logfire/_internal/integrations/flask.py index 16a5809c3..09dab37e0 100644 --- a/logfire/_internal/integrations/flask.py +++ b/logfire/_internal/integrations/flask.py @@ -5,6 +5,7 @@ from flask.app import Flask from opentelemetry.instrumentation.flask import FlaskInstrumentor +from logfire import Logfire from logfire._internal.utils import maybe_capture_server_headers if TYPE_CHECKING: @@ -27,10 +28,19 @@ class FlaskInstrumentKwargs(TypedDict, total=False): commenter_options: dict[str, str] | None -def instrument_flask(app: Flask, capture_headers: bool = False, **kwargs: Unpack[FlaskInstrumentKwargs]): +def instrument_flask( + logfire_instance: Logfire, app: Flask, capture_headers: bool = False, **kwargs: Unpack[FlaskInstrumentKwargs] +): """Instrument `app` so that spans are automatically created for each request. See the `Logfire.instrument_flask` method for details. """ maybe_capture_server_headers(capture_headers) - FlaskInstrumentor().instrument_app(app, **kwargs) # type: ignore[reportUnknownMemberType] + FlaskInstrumentor().instrument_app( # type: ignore[reportUnknownMemberType] + app, + **{ # type: ignore + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + }, + ) diff --git a/logfire/_internal/integrations/httpx.py b/logfire/_internal/integrations/httpx.py index 870af5361..560665532 100644 --- a/logfire/_internal/integrations/httpx.py +++ b/logfire/_internal/integrations/httpx.py @@ -4,6 +4,8 @@ from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor +from logfire import Logfire + if TYPE_CHECKING: from typing import Awaitable, Callable, TypedDict, Unpack @@ -23,9 +25,15 @@ class HTTPXInstrumentKwargs(TypedDict, total=False): skip_dep_check: bool -def instrument_httpx(**kwargs: Unpack[HTTPXInstrumentKwargs]) -> None: +def instrument_httpx(logfire_instance: Logfire, **kwargs: Unpack[HTTPXInstrumentKwargs]) -> None: """Instrument the `httpx` module so that spans are automatically created for each request. See the `Logfire.instrument_httpx` method for details. """ - HTTPXClientInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType] + HTTPXClientInstrumentor().instrument( # type: ignore[reportUnknownMemberType] + **{ + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + } + ) diff --git a/logfire/_internal/integrations/psycopg.py b/logfire/_internal/integrations/psycopg.py index 0a181b8ec..95a58c160 100644 --- a/logfire/_internal/integrations/psycopg.py +++ b/logfire/_internal/integrations/psycopg.py @@ -8,6 +8,8 @@ from packaging.requirements import Requirement +from logfire import Logfire + if TYPE_CHECKING: # pragma: no cover from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor @@ -28,7 +30,9 @@ class PsycopgInstrumentKwargs(TypedDict, total=False): PACKAGE_NAMES = ('psycopg', 'psycopg2') -def instrument_psycopg(conn_or_module: Any = None, **kwargs: Unpack[PsycopgInstrumentKwargs]) -> None: +def instrument_psycopg( + logfire_instance: Logfire, conn_or_module: Any = None, **kwargs: Unpack[PsycopgInstrumentKwargs] +) -> None: """Instrument a `psycopg` connection or module so that spans are automatically created for each query. See the `Logfire.instrument_psycopg` method for details. @@ -37,13 +41,13 @@ def instrument_psycopg(conn_or_module: Any = None, **kwargs: Unpack[PsycopgInstr # By default, instrument whichever libraries are installed. for package in PACKAGE_NAMES: if find_spec(package): # pragma: no branch - instrument_psycopg(package, **kwargs) + instrument_psycopg(logfire_instance, package, **kwargs) return elif conn_or_module in PACKAGE_NAMES: - _instrument_psycopg(conn_or_module, **kwargs) + _instrument_psycopg(logfire_instance, conn_or_module, **kwargs) return elif isinstance(conn_or_module, ModuleType): - instrument_psycopg(conn_or_module.__name__, **kwargs) + instrument_psycopg(logfire_instance, conn_or_module.__name__, **kwargs) return else: # Given an object that's not a module or string, @@ -55,13 +59,15 @@ def instrument_psycopg(conn_or_module: Any = None, **kwargs: Unpack[PsycopgInstr raise TypeError( f'Extra keyword arguments are only supported when instrumenting the {package} module, not a connection.' ) - _instrument_psycopg(package, conn_or_module, **kwargs) + _instrument_psycopg(logfire_instance, package, conn_or_module, **kwargs) return raise ValueError(f"Don't know how to instrument {conn_or_module!r}") -def _instrument_psycopg(name: str, conn: Any = None, **kwargs: Unpack[PsycopgInstrumentKwargs]) -> None: +def _instrument_psycopg( + logfire_instance: Logfire, name: str, conn: Any = None, **kwargs: Unpack[PsycopgInstrumentKwargs] +) -> None: try: instrumentor_module = importlib.import_module(f'opentelemetry.instrumentation.{name}') except ImportError: @@ -83,10 +89,17 @@ def _instrument_psycopg(name: str, conn: Any = None, **kwargs: Unpack[PsycopgIns # OTEL looks for __libpq_version__ which only exists in psycopg2. mod.__libpq_version__ = psycopg.pq.version() # type: ignore - instrumentor.instrument(skip_dep_check=skip_dep_check, **kwargs) + instrumentor.instrument( + skip_dep_check=skip_dep_check, + **{ + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + }, + ) else: # instrument_connection doesn't have a skip_dep_check argument. - instrumentor.instrument_connection(conn) + instrumentor.instrument_connection(conn, tracer_provider=logfire_instance.config.get_tracer_provider()) def check_version(name: str, version: str, instrumentor: Instrumentor) -> bool: diff --git a/logfire/_internal/integrations/requests.py b/logfire/_internal/integrations/requests.py index b66fd9ae2..549b82a77 100644 --- a/logfire/_internal/integrations/requests.py +++ b/logfire/_internal/integrations/requests.py @@ -2,10 +2,19 @@ from opentelemetry.instrumentation.requests import RequestsInstrumentor +from logfire import Logfire -def instrument_requests(excluded_urls: Optional[str] = None, **kwargs: Any): + +def instrument_requests(logfire_instance: Logfire, excluded_urls: Optional[str] = None, **kwargs: Any): """Instrument the `requests` module so that spans are automatically created for each request. See the `Logfire.instrument_requests` method for details. """ - RequestsInstrumentor().instrument(excluded_urls=excluded_urls, **kwargs) # type: ignore[reportUnknownMemberType] + RequestsInstrumentor().instrument( # type: ignore[reportUnknownMemberType] + excluded_urls=excluded_urls, + **{ + 'tracer_provider': logfire_instance.config.get_tracer_provider(), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + }, + ) diff --git a/logfire/_internal/integrations/starlette.py b/logfire/_internal/integrations/starlette.py index 2a388783c..0eaae0146 100644 --- a/logfire/_internal/integrations/starlette.py +++ b/logfire/_internal/integrations/starlette.py @@ -34,6 +34,9 @@ def instrument_starlette( maybe_capture_server_headers(capture_headers) StarletteInstrumentor().instrument_app( # type: ignore[reportUnknownMemberType] app, - tracer_provider=tweak_asgi_spans_tracer_provider(logfire_instance, record_send_receive), - **kwargs, + **{ # type: ignore + 'tracer_provider': tweak_asgi_spans_tracer_provider(logfire_instance, record_send_receive), + 'meter_provider': logfire_instance.config.get_meter_provider(), + **kwargs, + }, ) diff --git a/logfire/_internal/main.py b/logfire/_internal/main.py index b97cd524b..f3cb8853f 100644 --- a/logfire/_internal/main.py +++ b/logfire/_internal/main.py @@ -115,6 +115,7 @@ def config(self) -> LogfireConfig: @cached_property def _tracer_provider(self) -> ProxyTracerProvider: + self._config.warn_if_not_initialized('No logs or spans will be created') return self._config.get_tracer_provider() @cached_property @@ -843,6 +844,7 @@ def instrument_pydantic( if isinstance(exclude, str): exclude = {exclude} + # TODO instrument using this instance, i.e. pass `self` somewhere, rather than always using the global instance set_pydantic_plugin_config( PydanticPlugin( record=record, @@ -1078,7 +1080,7 @@ def instrument_asyncpg(self, **kwargs: Unpack[AsyncPGInstrumentKwargs]) -> None: from .integrations.asyncpg import instrument_asyncpg self._warn_if_not_initialized_for_instrumentation() - return instrument_asyncpg(**kwargs) + return instrument_asyncpg(self, **kwargs) def instrument_httpx(self, **kwargs: Unpack[HTTPXInstrumentKwargs]) -> None: """Instrument the `httpx` module so that spans are automatically created for each request. @@ -1090,7 +1092,7 @@ def instrument_httpx(self, **kwargs: Unpack[HTTPXInstrumentKwargs]) -> None: from .integrations.httpx import instrument_httpx self._warn_if_not_initialized_for_instrumentation() - return instrument_httpx(**kwargs) + return instrument_httpx(self, **kwargs) def instrument_celery(self, **kwargs: Unpack[CeleryInstrumentKwargs]) -> None: """Instrument `celery` so that spans are automatically created for each task. @@ -1102,7 +1104,7 @@ def instrument_celery(self, **kwargs: Unpack[CeleryInstrumentKwargs]) -> None: from .integrations.celery import instrument_celery self._warn_if_not_initialized_for_instrumentation() - return instrument_celery(**kwargs) + return instrument_celery(self, **kwargs) def instrument_django( self, @@ -1147,6 +1149,7 @@ def instrument_django( self._warn_if_not_initialized_for_instrumentation() return instrument_django( + self, capture_headers=capture_headers, is_sql_commentor_enabled=is_sql_commentor_enabled, request_hook=request_hook, @@ -1166,7 +1169,7 @@ def instrument_requests(self, excluded_urls: str | None = None, **kwargs: Any) - from .integrations.requests import instrument_requests self._warn_if_not_initialized_for_instrumentation() - return instrument_requests(excluded_urls=excluded_urls, **kwargs) + return instrument_requests(self, excluded_urls=excluded_urls, **kwargs) def instrument_psycopg(self, conn_or_module: Any = None, **kwargs: Unpack[PsycopgInstrumentKwargs]) -> None: """Instrument a `psycopg` connection or module so that spans are automatically created for each query. @@ -1190,7 +1193,7 @@ def instrument_psycopg(self, conn_or_module: Any = None, **kwargs: Unpack[Psycop from .integrations.psycopg import instrument_psycopg self._warn_if_not_initialized_for_instrumentation() - return instrument_psycopg(conn_or_module, **kwargs) + return instrument_psycopg(self, conn_or_module, **kwargs) def instrument_flask( self, app: Flask, *, capture_headers: bool = False, **kwargs: Unpack[FlaskInstrumentKwargs] @@ -1206,7 +1209,7 @@ def instrument_flask( from .integrations.flask import instrument_flask self._warn_if_not_initialized_for_instrumentation() - return instrument_flask(app, capture_headers=capture_headers, **kwargs) + return instrument_flask(self, app, capture_headers=capture_headers, **kwargs) def instrument_starlette( self, @@ -1250,7 +1253,7 @@ def instrument_aiohttp_client(self, **kwargs: Any) -> None: from .integrations.aiohttp_client import instrument_aiohttp_client self._warn_if_not_initialized_for_instrumentation() - return instrument_aiohttp_client(**kwargs) + return instrument_aiohttp_client(self, **kwargs) def instrument_sqlalchemy(self, **kwargs: Unpack[SQLAlchemyInstrumentKwargs]) -> None: """Instrument the `sqlalchemy` module so that spans are automatically created for each query. From 0faf44023ffc47b97177bdfa8661f290a7f733d4 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Thu, 17 Oct 2024 13:05:35 +0200 Subject: [PATCH 3/3] pass providers to instrumentations --- logfire/_internal/integrations/mysql.py | 16 +++------ .../_internal/integrations/system_metrics.py | 2 +- logfire/_internal/main.py | 34 ++++++++++++++++--- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/logfire/_internal/integrations/mysql.py b/logfire/_internal/integrations/mysql.py index b4e24dfde..16604b6fc 100644 --- a/logfire/_internal/integrations/mysql.py +++ b/logfire/_internal/integrations/mysql.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from opentelemetry.instrumentation.mysql import MySQLInstrumentor +from opentelemetry.trace import TracerProvider if TYPE_CHECKING: from mysql.connector.abstracts import MySQLConnectionAbstract @@ -16,22 +17,15 @@ class MySQLInstrumentKwargs(TypedDict, total=False): def instrument_mysql( + *, conn: MySQLConnection = None, + tracer_provider: TracerProvider, **kwargs: Unpack[MySQLInstrumentKwargs], ) -> MySQLConnection: """Instrument the `mysql` module or a specific MySQL connection so that spans are automatically created for each operation. - This function uses the OpenTelemetry MySQL Instrumentation library to instrument either the entire `mysql` module or a specific MySQL connection. - - Args: - conn: The MySQL connection to instrument. If None, the entire `mysql` module is instrumented. - **kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods. - - Returns: - If a connection is provided, returns the instrumented connection. If no connection is provided, returns None. - See the `Logfire.instrument_mysql` method for details. """ if conn is not None: - return MySQLInstrumentor().instrument_connection(conn) # type: ignore[reportUnknownMemberType] - return MySQLInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType] + return MySQLInstrumentor().instrument_connection(conn, tracer_provider=tracer_provider) # type: ignore[reportUnknownMemberType] + return MySQLInstrumentor().instrument(**kwargs, tracer_provider=tracer_provider) # type: ignore[reportUnknownMemberType] diff --git a/logfire/_internal/integrations/system_metrics.py b/logfire/_internal/integrations/system_metrics.py index b2ad889e0..e1eaa4e80 100644 --- a/logfire/_internal/integrations/system_metrics.py +++ b/logfire/_internal/integrations/system_metrics.py @@ -141,7 +141,7 @@ def instrument_system_metrics(logfire_instance: Logfire, config: Config | None = del config['process.runtime.cpu.utilization'] instrumentor = SystemMetricsInstrumentor(config=config) # type: ignore - instrumentor.instrument() # type: ignore + instrumentor.instrument(meter_provider=logfire_instance.config.get_meter_provider()) # type: ignore def measure_simple_cpu_utilization(logfire_instance: Logfire): diff --git a/logfire/_internal/main.py b/logfire/_internal/main.py index f3cb8853f..5e8980d35 100644 --- a/logfire/_internal/main.py +++ b/logfire/_internal/main.py @@ -1265,7 +1265,13 @@ def instrument_sqlalchemy(self, **kwargs: Unpack[SQLAlchemyInstrumentKwargs]) -> from .integrations.sqlalchemy import instrument_sqlalchemy self._warn_if_not_initialized_for_instrumentation() - return instrument_sqlalchemy(**kwargs) + return instrument_sqlalchemy( + **{ # type: ignore + 'tracer_provider': self._config.get_tracer_provider(), + 'meter_provider': self._config.get_meter_provider(), + **kwargs, + }, + ) def instrument_pymongo(self, **kwargs: Unpack[PymongoInstrumentKwargs]) -> None: """Instrument the `pymongo` module so that spans are automatically created for each operation. @@ -1277,7 +1283,13 @@ def instrument_pymongo(self, **kwargs: Unpack[PymongoInstrumentKwargs]) -> None: from .integrations.pymongo import instrument_pymongo self._warn_if_not_initialized_for_instrumentation() - return instrument_pymongo(**kwargs) + return instrument_pymongo( + **{ # type: ignore + 'tracer_provider': self._config.get_tracer_provider(), + 'meter_provider': self._config.get_meter_provider(), + **kwargs, + }, + ) def instrument_redis(self, capture_statement: bool = False, **kwargs: Unpack[RedisInstrumentKwargs]) -> None: """Instrument the `redis` module so that spans are automatically created for each operation. @@ -1293,7 +1305,14 @@ def instrument_redis(self, capture_statement: bool = False, **kwargs: Unpack[Red from .integrations.redis import instrument_redis self._warn_if_not_initialized_for_instrumentation() - return instrument_redis(capture_statement=capture_statement, **kwargs) + return instrument_redis( + capture_statement=capture_statement, + **{ # type: ignore + 'tracer_provider': self._config.get_tracer_provider(), + 'meter_provider': self._config.get_meter_provider(), + **kwargs, + }, + ) def instrument_mysql( self, @@ -1317,7 +1336,14 @@ def instrument_mysql( from .integrations.mysql import instrument_mysql self._warn_if_not_initialized_for_instrumentation() - return instrument_mysql(conn, **kwargs) + return instrument_mysql( + conn=conn, + **{ # type: ignore + 'tracer_provider': self._config.get_tracer_provider(), + 'meter_provider': self._config.get_meter_provider(), + **kwargs, + }, + ) def instrument_system_metrics( self, config: SystemMetricsConfig | None = None, base: SystemMetricsBase = 'basic'