Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add local parameter to logfire.configure() #508

Merged
merged 5 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions logfire/_internal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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] = {
Expand Down Expand Up @@ -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.

Expand All @@ -889,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')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved these warnings to prevent logfire.instrument_fastapi emitting similar warnings 3 times.

return self._tracer_provider

def get_meter_provider(self) -> ProxyMeterProvider:
Expand All @@ -900,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):
Expand Down
12 changes: 10 additions & 2 deletions logfire/_internal/integrations/aiohttp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Copy link
Contributor Author

@alexmojaki alexmojaki Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn on how to do this. On the one hand I don't want to completely prevent users from passing custom providers. We even do that in our own code (passing a NoopMeterProvider to disable fastapi metrics), and I've recommended another user to do the same. On the other hand I don't want to encourage or document it. So this makes it secretly possible to override the logfire providers with custom ones. Which is a mess for type checking.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean we're using kwargs already so this seems like a step better

'meter_provider': logfire_instance.config.get_meter_provider(),
**kwargs,
},
)
12 changes: 10 additions & 2 deletions logfire/_internal/integrations/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@

from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor

from logfire import Logfire

if TYPE_CHECKING:
from typing_extensions import TypedDict, Unpack

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,
}
)
12 changes: 10 additions & 2 deletions logfire/_internal/integrations/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@

from opentelemetry.instrumentation.celery import CeleryInstrumentor

from logfire import Logfire

if TYPE_CHECKING:
from typing_extensions import TypedDict, Unpack

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,
}
)
11 changes: 9 additions & 2 deletions logfire/_internal/integrations/django.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Any

from logfire import Logfire
from logfire._internal.utils import maybe_capture_server_headers

try:
Expand All @@ -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,
}
)
6 changes: 5 additions & 1 deletion logfire/_internal/integrations/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
14 changes: 12 additions & 2 deletions logfire/_internal/integrations/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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,
},
)
12 changes: 10 additions & 2 deletions logfire/_internal/integrations/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor

from logfire import Logfire

if TYPE_CHECKING:
from typing import Awaitable, Callable, TypedDict, Unpack

Expand All @@ -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,
}
)
16 changes: 5 additions & 11 deletions logfire/_internal/integrations/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
29 changes: 21 additions & 8 deletions logfire/_internal/integrations/psycopg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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:
Expand Down
Loading