Skip to content

Commit

Permalink
Merge branch 'main' into simplify-publish
Browse files Browse the repository at this point in the history
  • Loading branch information
alexmojaki authored Nov 13, 2024
2 parents d183bdf + 07d2e54 commit 78eb2a5
Show file tree
Hide file tree
Showing 28 changed files with 697 additions and 425 deletions.
13 changes: 13 additions & 0 deletions logfire/_internal/ast_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ def rewrite_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef, qualnam
# so it's still recognized as a docstring.
new_body.append(body.pop(0))

# Ignore functions with a trivial/empty body:
# - If `body` is empty, that means it originally was just a docstring that got popped above.
# - If `body` is just a single `pass` statement
# - If `body` is just a constant expression, particularly an ellipsis (`...`)
if not body or (
len(body) == 1
and (
isinstance(body[0], ast.Pass)
or (isinstance(body[0], ast.Expr) and isinstance(body[0].value, ast.Constant))
)
):
return node

span = ast.With(
items=[
ast.withitem(
Expand Down
10 changes: 8 additions & 2 deletions logfire/_internal/integrations/aiohttp_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from typing import Any

from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor

try:
from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_aiohttp_client()` requires the `opentelemetry-instrumentation-aiohttp-client` package.\n'
'You can install this with:\n'
" pip install 'logfire[aiohttp]'"
)
from logfire import Logfire


Expand Down
9 changes: 8 additions & 1 deletion logfire/_internal/integrations/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from typing import TYPE_CHECKING

from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
try:
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_asyncpg()` requires the `opentelemetry-instrumentation-asyncpg` package.\n'
'You can install this with:\n'
" pip install 'logfire[asyncpg]'"
)

from logfire import Logfire

Expand Down
9 changes: 8 additions & 1 deletion logfire/_internal/integrations/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from typing import TYPE_CHECKING

from opentelemetry.instrumentation.celery import CeleryInstrumentor
try:
from opentelemetry.instrumentation.celery import CeleryInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_celery()` requires the `opentelemetry-instrumentation-celery` package.\n'
'You can install this with:\n'
" pip install 'logfire[celery]'"
)

from logfire import Logfire

Expand Down
10 changes: 9 additions & 1 deletion logfire/_internal/integrations/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
from typing import TYPE_CHECKING

from flask.app import Flask
from opentelemetry.instrumentation.flask import FlaskInstrumentor

try:
from opentelemetry.instrumentation.flask import FlaskInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_flask()` requires the `opentelemetry-instrumentation-flask` package.\n'
'You can install this with:\n'
" pip install 'logfire[flask]'"
)

from logfire import Logfire
from logfire._internal.utils import maybe_capture_server_headers
Expand Down
9 changes: 8 additions & 1 deletion logfire/_internal/integrations/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from typing import TYPE_CHECKING, Any

from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
try:
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_httpx()` requires the `opentelemetry-instrumentation-httpx` package.\n'
'You can install this with:\n'
" pip install 'logfire[httpx]'"
)

from logfire import Logfire

Expand Down
10 changes: 9 additions & 1 deletion logfire/_internal/integrations/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

from typing import TYPE_CHECKING

from opentelemetry.instrumentation.mysql import MySQLInstrumentor
from opentelemetry.trace import TracerProvider

try:
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_mysql()` requires the `opentelemetry-instrumentation-mysql` package.\n'
'You can install this with:\n'
" pip install 'logfire[mysql]'"
)

if TYPE_CHECKING:
from mysql.connector.abstracts import MySQLConnectionAbstract
from mysql.connector.pooling import PooledMySQLConnection
Expand Down
9 changes: 8 additions & 1 deletion logfire/_internal/integrations/pymongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from typing import TYPE_CHECKING, Any

from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
try:
from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_pymongo()` requires the `opentelemetry-instrumentation-pymongo` package.\n'
'You can install this with:\n'
" pip install 'logfire[pymongo]'"
)

if TYPE_CHECKING:
from pymongo.monitoring import CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent
Expand Down
9 changes: 8 additions & 1 deletion logfire/_internal/integrations/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
import functools
from typing import TYPE_CHECKING, Any

from opentelemetry.instrumentation.redis import RedisInstrumentor
try:
from opentelemetry.instrumentation.redis import RedisInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_redis()` requires the `opentelemetry-instrumentation-redis` package.\n'
'You can install this with:\n'
" pip install 'logfire[redis]'"
)

from logfire._internal.constants import ATTRIBUTES_MESSAGE_KEY
from logfire._internal.utils import truncate_string
Expand Down
9 changes: 8 additions & 1 deletion logfire/_internal/integrations/requests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from typing import Any, Optional

from opentelemetry.instrumentation.requests import RequestsInstrumentor
try:
from opentelemetry.instrumentation.requests import RequestsInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_requests()` requires the `opentelemetry-instrumentation-requests` package.\n'
'You can install this with:\n'
" pip install 'logfire[requests]'"
)

from logfire import Logfire

Expand Down
9 changes: 8 additions & 1 deletion logfire/_internal/integrations/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from typing import TYPE_CHECKING

from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
try:
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_sqlalchemy()` requires the `opentelemetry-instrumentation-sqlalchemy` package.\n'
'You can install this with:\n'
" pip install 'logfire[sqlalchemy]'"
)

if TYPE_CHECKING:
from sqlalchemy import Engine
Expand Down
10 changes: 9 additions & 1 deletion logfire/_internal/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

from typing import TYPE_CHECKING

from opentelemetry.instrumentation.starlette import StarletteInstrumentor
from starlette.applications import Starlette

try:
from opentelemetry.instrumentation.starlette import StarletteInstrumentor
except ModuleNotFoundError:
raise RuntimeError(
'`logfire.instrument_starlette()` requires the `opentelemetry-instrumentation-starlette` package.\n'
'You can install this with:\n'
" pip install 'logfire[starlette]'"
)

from logfire import Logfire
from logfire._internal.integrations.asgi import tweak_asgi_spans_tracer_provider
from logfire._internal.utils import maybe_capture_server_headers
Expand Down
7 changes: 6 additions & 1 deletion logfire/_internal/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import re
import uuid
from collections import deque
from contextlib import suppress
from decimal import Decimal
from enum import Enum
from functools import lru_cache
Expand Down Expand Up @@ -350,9 +351,13 @@ def _properties(properties: dict[str, Any], seen: set[int]) -> JsonDict:


def _custom_object_schema(obj: Any, datatype_name: str, keys: Iterable[str], seen: set[int]) -> JsonDict:
properties: dict[str, Any] = {}
for key in keys:
with suppress(Exception):
properties[key] = getattr(obj, key)
return {
'type': 'object',
'title': obj.__class__.__name__,
'x-python-datatype': datatype_name,
**_properties({key: getattr(obj, key) for key in keys}, seen),
**_properties(properties, seen),
}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ dev = [
"numpy>=2.0; python_version >= '3.9' and python_version < '3.12'",
"numpy<1.24.4; python_version < '3.9'",
"pytest-recording>=0.13.2",
"vcrpy>=6",
"uvicorn>=0.30.6",
"logfire-api",
"requests",
Expand Down
16 changes: 16 additions & 0 deletions tests/otel_integrations/test_aiohttp_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import importlib
from unittest import mock

import aiohttp
import pytest
from inline_snapshot import snapshot

import logfire
import logfire._internal.integrations.aiohttp_client


# TODO real test
Expand All @@ -12,3 +17,14 @@ async def test_instrument_aiohttp():
assert cls.__init__ is original_init
logfire.instrument_aiohttp_client()
assert cls.__init__ is not original_init


def test_missing_opentelemetry_dependency() -> None:
with mock.patch.dict('sys.modules', {'opentelemetry.instrumentation.aiohttp_client': None}):
with pytest.raises(RuntimeError) as exc_info:
importlib.reload(logfire._internal.integrations.aiohttp_client)
assert str(exc_info.value) == snapshot("""\
`logfire.instrument_aiohttp_client()` requires the `opentelemetry-instrumentation-aiohttp-client` package.
You can install this with:
pip install 'logfire[aiohttp]'\
""")
17 changes: 17 additions & 0 deletions tests/otel_integrations/test_asyncpg.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import importlib
from unittest import mock

import asyncpg
import pytest
from inline_snapshot import snapshot
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor

import logfire
import logfire._internal.integrations.asyncpg


def test_asyncpg() -> None:
Expand All @@ -10,3 +16,14 @@ def test_asyncpg() -> None:
assert original_execute is not asyncpg.Connection.execute # type: ignore[reportUnknownMemberType]
AsyncPGInstrumentor().uninstrument() # type: ignore[reportUnknownMemberType]
assert original_execute is asyncpg.Connection.execute # type: ignore[reportUnknownMemberType]


def test_missing_opentelemetry_dependency() -> None:
with mock.patch.dict('sys.modules', {'opentelemetry.instrumentation.asyncpg': None}):
with pytest.raises(RuntimeError) as exc_info:
importlib.reload(logfire._internal.integrations.asyncpg)
assert str(exc_info.value) == snapshot("""\
`logfire.instrument_asyncpg()` requires the `opentelemetry-instrumentation-asyncpg` package.
You can install this with:
pip install 'logfire[asyncpg]'\
""")
14 changes: 14 additions & 0 deletions tests/otel_integrations/test_celery.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import importlib
import logging
import sys
from typing import Generator, Iterator
from unittest import mock

import pytest
from celery import Celery
Expand All @@ -12,12 +14,24 @@
from testcontainers.redis import RedisContainer

import logfire
import logfire._internal.integrations.celery
from logfire.testing import TestExporter

# TODO find a better solution
pytestmark = pytest.mark.skipif(sys.version_info < (3, 9), reason='Redis testcontainers has problems in 3.8')


def test_missing_opentelemetry_dependency() -> None:
with mock.patch.dict('sys.modules', {'opentelemetry.instrumentation.celery': None}):
with pytest.raises(RuntimeError) as exc_info:
importlib.reload(logfire._internal.integrations.celery)
assert str(exc_info.value) == snapshot("""\
`logfire.instrument_celery()` requires the `opentelemetry-instrumentation-celery` package.
You can install this with:
pip install 'logfire[celery]'\
""")


@pytest.fixture(scope='module', autouse=True)
def redis_container() -> Generator[RedisContainer, None, None]:
with RedisContainer('redis:latest') as redis:
Expand Down
16 changes: 16 additions & 0 deletions tests/otel_integrations/test_flask.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import importlib
from unittest import mock

import opentelemetry.instrumentation.flask
import pytest
from flask import Flask
from inline_snapshot import snapshot
from opentelemetry.propagate import inject
from werkzeug.test import Client

import logfire
import logfire._internal.integrations.flask
from logfire.testing import TestExporter, TimeGenerator


Expand Down Expand Up @@ -139,3 +144,14 @@ def homepage(): # type: ignore
},
]
)


def test_missing_opentelemetry_dependency() -> None:
with mock.patch.dict('sys.modules', {'opentelemetry.instrumentation.flask': None}):
with pytest.raises(RuntimeError) as exc_info:
importlib.reload(logfire._internal.integrations.flask)
assert str(exc_info.value) == snapshot("""\
`logfire.instrument_flask()` requires the `opentelemetry-instrumentation-flask` package.
You can install this with:
pip install 'logfire[flask]'\
""")
15 changes: 15 additions & 0 deletions tests/otel_integrations/test_httpx.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import importlib
from unittest import mock

import httpx
import pytest
from httpx import Request
from inline_snapshot import snapshot
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor

import logfire
import logfire._internal.integrations.httpx
from logfire.testing import TestExporter


Expand Down Expand Up @@ -72,3 +76,14 @@ def handler(request: Request):
},
]
)


def test_missing_opentelemetry_dependency() -> None:
with mock.patch.dict('sys.modules', {'opentelemetry.instrumentation.httpx': None}):
with pytest.raises(RuntimeError) as exc_info:
importlib.reload(logfire._internal.integrations.httpx)
assert str(exc_info.value) == snapshot("""\
`logfire.instrument_httpx()` requires the `opentelemetry-instrumentation-httpx` package.
You can install this with:
pip install 'logfire[httpx]'\
""")
Loading

0 comments on commit 78eb2a5

Please sign in to comment.