8000 Upgrade to OpenTelemetry SDK 1.34.0 by alexmojaki · Pull Request #1120 · pydantic/logfire · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Upgrade to OpenTelemetry SDK 1.34.0 #1120

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

Merged
merged 11 commits into from
Jun 5, 2025
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
2 changes: 0 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ jobs:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
pydantic-version: ['2.11']
include:
- python-version: '3.8'
pydantic-version: '2.10'
- python-version: '3.12'
pydantic-version: '2.4'
env:
Expand Down
27 changes: 9 additions & 18 deletions logfire/_internal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio, Sampler
from opentelemetry.semconv.resource import ResourceAttributes
from rich.console import Console
from rich.prompt import Confirm, IntPrompt, Prompt
from typing_extensions import Self, Unpack
Expand Down Expand Up @@ -574,10 +573,6 @@ def _load_configuration(
self.inspect_arguments = param_manager.load_param('inspect_arguments', inspect_arguments)
self.distributed_tracing = param_manager.load_param('distributed_tracing', distributed_tracing)
self.ignore_no_config = param_manager.load_param('ignore_no_config')
if self.inspect_arguments and sys.version_info[:2] <= (3, 8):
raise LogfireConfigError(
'Inspecting arguments is only supported in Python 3.9+ and only recommended in Python 3.11+.'
)

# We save `scrubbing` just so that it can be serialized and deserialized.
if isinstance(scrubbing, dict):
Expand Down Expand Up @@ -765,12 +760,12 @@ def _initialize(self) -> None:

with suppress_instrumentation():
otel_resource_attributes: dict[str, Any] = {
ResourceAttributes.SERVICE_NAME: self.service_name,
ResourceAttributes.PROCESS_PID: os.getpid(),
'service.name': self.service_name,
'process.pid': os.getpid(),
# https://opentelemetry.io/docs/specs/semconv/resource/process/#python-runtimes
ResourceAttributes.PROCESS_RUNTIME_NAME: sys.implementation.name,
ResourceAttributes.PROCESS_RUNTIME_VERSION: get_runtime_version(),
ResourceAttributes.PROCESS_RUNTIME_DESCRIPTION: sys.version,
'process.runtime.name': sys.implementation.name,
'process.runtime.version': get_runtime_version(),
'process.runtime.description': sys.version,
# Having this giant blob of data associated with every span/metric causes various problems so it's
# disabled for now, but we may want to re-enable something like it in the future
# RESOURCE_ATTRIBUTES_PACKAGE_VERSIONS: json.dumps(collect_package_info(), separators=(',', ':')),
Expand All @@ -785,7 +780,7 @@ def _initialize(self) -> None:
if self.code_source.root_path:
otel_resource_attributes[RESOURCE_ATTRIBUTES_CODE_ROOT_PATH] = self.code_source.root_path
if self.service_version:
otel_resource_attributes[ResourceAttributes.SERVICE_VERSION] = self.service_version
otel_resource_attributes['service.version'] = self.service_version
if self.environment:
otel_resource_attributes[RESOURCE_ATTRIBUTES_DEPLOYMENT_ENVIRONMENT_NAME] = self.environment
otel_resource_attributes_from_env = os.getenv(OTEL_RESOURCE_ATTRIBUTES)
Expand Down Expand Up @@ -813,7 +808,7 @@ def _initialize(self) -> None:
# Currently there's a newer version with some differences here:
# https://github.com/open-telemetry/semantic-conventions/blob/e44693245eef815071402b88c3a44a8f7f8f24c8/docs/resource/README.md#service-experimental
# Both recommend generating a UUID.
resource = Resource({ResourceAttributes.SERVICE_INSTANCE_ID: uuid4().hex}).merge(resource)
resource = Resource({'service.instance.id': uuid4().hex}).merge(resource)

head = self.sampling.head
sampler: Sampler | None = None
Expand Down Expand Up @@ -843,12 +838,8 @@ def _initialize(self) -> None:

def add_span_processor(span_processor: SpanProcessor) -> None:
main_multiprocessor.add_span_processor(span_processor)
inner_span_processor = span_processor
while isinstance(p := getattr(inner_span_processor, 'processor', None), SpanProcessor):
inner_span_processor = p

has_pending = isinstance(
getattr(inner_span_processor, 'span_exporter', None),
getattr(span_processor, 'span_exporter', None),
(TestExporter, RemovePendingSpansExporter, SimpleConsoleSpanExporter),
)
if has_pending:
Expand Down Expand Up @@ -1038,7 +1029,7 @@ def check_token():

def fix_pid(): # pragma: no cover
with handle_internal_errors:
new_resource = resource.merge(Resource({ResourceAttributes.PROCESS_PID: os.getpid()}))
new_resource = resource.merge(Resource({'process.pid': os.getpid()}))
tracer_provider._resource = new_resource # type: ignore
meter_provider._resource = new_resource # type: ignore
logger_provider._resource = new_resource # type: ignore
Expand Down
6 changes: 2 additions & 4 deletions logfire/_internal/db_statement_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import re
from typing import Any, Mapping

from opentelemetry.semconv.trace import SpanAttributes

MAX_QUERY_MESSAGE_LENGTH = 80


Expand All @@ -13,7 +11,7 @@ def message_from_db_statement(attributes: Mapping[str, Any], message: str | None

Returns: A new string to use as span message or None to keep the original message.
"""
db_statement = attributes.get(SpanAttributes.DB_STATEMENT)
db_statement = attributes.get('db.statement')
if not isinstance(db_statement, str):
# covers `None` and anything any other unexpected type
return None
Expand All @@ -25,7 +23,7 @@ def message_from_db_statement(attributes: Mapping[str, Any], message: str | None

db_statement = db_statement.strip()
if isinstance(message, str):
db_name = attributes.get(SpanAttributes.DB_NAME)
db_name = attributes.get('db.name')
if db_name and isinstance(db_name, str) and message.endswith(db_name):
operation = message[: -len(db_name) - 1]
else:
Expand Down
25 changes: 22 additions & 3 deletions logfire/_internal/exporters/dynamic_batch.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from __future__ import annotations

import os
from typing import cast
from typing import TYPE_CHECKING

from opentelemetry.sdk.environment_variables import OTEL_BSP_SCHEDULE_DELAY
from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.sdk.trace import ReadableSpan, Span
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter

from logfire._internal.exporters.wrapper import WrapperSpanProcessor

if TYPE_CHECKING:
from opentelemetry.sdk._shared_internal import BatchProcessor


class DynamicBatchSpanProcessor(WrapperSpanProcessor):
"""A wrapper around a BatchSpanProcessor that dynamically adjusts the schedule delay.
Expand All @@ -18,6 +21,8 @@ class DynamicBatchSpanProcessor(WrapperSpanProcessor):
This makes the initial experience of the SDK more responsive.
"""

processor: BatchSpanProcessor # type: ignore

def __init__(self, exporter: SpanExporter) -> None:
self.final_delay = float(os.environ.get(OTEL_BSP_SCHEDULE_DELAY) or 500)
# Start with the configured value immediately if it's less than 100ms.
Expand All @@ -28,5 +33,19 @@ def __init__(self, exporter: SpanExporter) -> None:
def on_end(self, span: ReadableSpan) -> None:
self.num_processed += 1
if self.num_processed == 10:
cast(BatchSpanProcessor, self.processor).schedule_delay_millis = self.final_delay
if hasattr(self.batch_processor, '_schedule_delay_millis'):
self.batch_processor._schedule_delay = self.final_delay / 1e3 # type: ignore
else: # pragma: no cover
self.processor.schedule_delay_millis = self.final_delay # type: ignore
super().on_end(span)

@property
def batch_processor(self) -> BatchSpanProcessor | BatchProcessor[Span]:
return getattr(self.processor, '_batch_processor', self.processor)

@property
def span_exporter(self) -> SpanExporter:
if isinstance(self.batch_processor, BatchSpanProcessor): # pragma: no cover
return self.batch_processor.span_exporter # type: ignore
else:
return self.batch_processor._exporter # type: ignore
17 changes: 7 additions & 10 deletions logfire/_internal/exporters/processor_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from opentelemetry import context
from opentelemetry.sdk.trace import ReadableSpan, Span
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import SpanKind, Status, StatusCode

import logfire
Expand Down Expand Up @@ -121,7 +120,7 @@ def _tweak_sqlalchemy_connect_spans(span: ReadableSpanDict) -> None:
attributes = span['attributes']
# We never expect db.statement to be in the attributes here.
# This is just to be extra sure that we're not accidentally hiding an actual query span.
if SpanAttributes.DB_SYSTEM not in attributes or SpanAttributes.DB_STATEMENT in attributes: # pragma: no cover
if 'db.system' not in attributes or 'db.statement' in attributes: # pragma: no cover
return
span['attributes'] = {**attributes, **log_level_attributes('debug')}

Expand Down Expand Up @@ -196,17 +195,17 @@ def _tweak_http_spans(span: ReadableSpanDict):
if name != attributes.get(ATTRIBUTES_MESSAGE_KEY): # pragma: no cover
return

method = attributes.get(SpanAttributes.HTTP_METHOD)
route = attributes.get(SpanAttributes.HTTP_ROUTE)
target = attributes.get(SpanAttributes.HTTP_TARGET)
url: Any = attributes.get(SpanAttributes.HTTP_URL)
method = attributes.get('http.method')
route = attributes.get('http.route')
target = attributes.get('http.target')
url: Any = attributes.get('http.url')
if not (method or route or target or url):
return

if not target and url and isinstance(url, str):
try:
target = urlparse(url).path
span['attributes'] = attributes = {**attributes, SpanAttributes.HTTP_TARGET: target}
span['attributes'] = attributes = {**attributes, 'http.target': target}
except Exception: # pragma: no cover
pass

Expand All @@ -224,9 +223,7 @@ def _tweak_http_spans(span: ReadableSpanDict):
if span['kind'] == SpanKind.CLIENT:
# For outgoing requests, we also want the domain, not just the path.
server_name: Any = (
attributes.get(SpanAttributes.SERVER_ADDRESS)
or attributes.get(SpanAttributes.HTTP_SERVER_NAME)
or attributes.get(SpanAttributes.HTTP_HOST)
attributes.get('server.address') or attributes.get('http.server_name') or attributes.get('http.host')
)
if not server_name:
try:
Expand Down
14 changes: 5 additions & 9 deletions logfire/_internal/exporters/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
from opentelemetry.sdk._logs.export import InMemoryLogExporter
from opentelemetry.sdk.trace import Event, ReadableSpan
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.semconv.trace import SpanAttributes

from ..constants import ATTRIBUTES_SPAN_TYPE_KEY

Expand Down Expand Up @@ -82,15 +80,13 @@ def build_event(event: Event) -> dict[str, Any]:
res: dict[str, Any] = {'name': event.name, 'timestamp': event.timestamp}
if event.attributes: # pragma: no branch
res['attributes'] = attributes = dict(event.attributes)
if SpanAttributes.EXCEPTION_STACKTRACE in attributes:
if 'exception.stacktrace' in attributes:
last_line = next( # pragma: no branch
line.strip()
for line in reversed(
cast(str, event.attributes[SpanAttributes.EXCEPTION_STACKTRACE]).split('\n')
)
for line in reversed(cast(str, event.attributes['exception.stacktrace']).split('\n'))
if line.strip()
)
attributes[SpanAttributes.EXCEPTION_STACKTRACE] = last_line
attributes['exception.stacktrace'] = last_line
return res

def build_instrumentation_scope(span: ReadableSpan) -> dict[str, Any]:
Expand Down Expand Up @@ -148,10 +144,10 @@ def process_attribute(
if name == 'code.function':
if sys.version_info >= (3, 11) and strip_function_qualname:
return value.split('.')[-1]
if name == ResourceAttributes.PROCESS_PID:
if name == 'process.pid':
assert value == os.getpid()
return 1234
if name == ResourceAttributes.SERVICE_INSTANCE_ID:
if name == 'service.instance.id':
if re.match(r'^[0-9a-f]{32}$', value):
return '0' * 32
if parse_json_attributes and isinstance(value, str) and (value.startswith('{') or value.startswith('[')):
Expand Down
7 changes: 3 additions & 4 deletions logfire/_internal/integrations/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from fastapi import BackgroundTasks, FastAPI
from fastapi.routing import APIRoute, APIWebSocketRoute, Mount
from fastapi.security import SecurityScopes
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import Span
from starlette.requests import Request
from starlette.responses import Response
Expand Down Expand Up @@ -78,7 +77,7 @@ def instrument_fastapi(
'meter_provider': logfire_instance.config.get_meter_provider(),
**opentelemetry_kwargs,
}
FastAPIInstrumentor.instrument_app( # type: ignore
FastAPIInstrumentor.instrument_app(
app,
excluded_urls=excluded_urls,
server_request_hook=_server_request_hook(opentelemetry_kwargs.pop('server_request_hook', None)),
Expand Down Expand Up @@ -170,10 +169,10 @@ async def solve_dependencies(self, request: Request | WebSocket, original: Await
with self.logfire_instance.span('FastAPI arguments') as span:
with handle_internal_errors:
if isinstance(request, Request): # pragma: no branch
span.set_attribute(SpanAttributes.HTTP_METHOD, request.method)
span.set_attribute('http.method', request.method)
route: APIRoute | APIWebSocketRoute | None = request.scope.get('route')
if route: # pragma: no branch
span.set_attribute(SpanAttributes.HTTP_ROUTE, route.path)
span.set_attribute('http.route', route.path)
fastapi_route_attributes: dict[str, Any] = {'fastapi.route.name': route.name}
if isinstance(route, APIRoute): # pragma: no branch
fastapi_route_attributes['fastapi.route.operation_id'] = route.operation_id
Expand Down
12 changes: 11 additions & 1 deletion logfire/_internal/integrations/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,17 @@ def instrument_httpx(
)

tracer_provider = final_kwargs['tracer_provider']
instrumentor.instrument_client(client, tracer_provider, request_hook, response_hook) # type: ignore[reportArgumentType]
meter_provider = final_kwargs['meter_provider']
client_kwargs = dict(
tracer_provider=tracer_provider,
request_hook=request_hook,
response_hook=response_hook,
)
try:
instrumentor.instrument_client(client, meter_provider=meter_provider, **client_kwargs)
except TypeError: # pragma: no cover
# This is a fallback for older versions of opentelemetry-instrumentation-httpx
instrumentor.instrument_client(client, **client_kwargs)


class LogfireHttpxInfoMixin:
Expand Down
28 changes: 13 additions & 15 deletions logfire/_internal/scrubbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from opentelemetry.attributes import BoundedAttributes
from opentelemetry.sdk._logs import LogRecord
from opentelemetry.sdk.trace import Event
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import Link

from .constants import (
Expand Down Expand Up @@ -122,22 +121,21 @@ class BaseScrubber(ABC):
ATTRIBUTES_SCRUBBED_KEY,
RESOURCE_ATTRIBUTES_PACKAGE_VERSIONS,
*STACK_INFO_KEYS,
SpanAttributes.EXCEPTION_STACKTRACE,
SpanAttributes.EXCEPTION_TYPE,
SpanAttributes.EXCEPTION_MESSAGE,
SpanAttributes.SCHEMA_URL,
SpanAttributes.HTTP_METHOD,
SpanAttributes.HTTP_STATUS_CODE,
SpanAttributes.HTTP_SCHEME,
SpanAttributes.HTTP_URL,
SpanAttributes.HTTP_TARGET,
SpanAttributes.HTTP_ROUTE,
SpanAttributes.DB_STATEMENT,
'exception.stacktrace',
'exception.type',
'exception.message',
'http.method',
'http.status_code',
'http.scheme',
'http.url',
'http.target',
'http.route',
'db.statement',
'db.plan',
# Newer semantic conventions
SpanAttributes.URL_FULL,
SpanAttributes.URL_PATH,
SpanAttributes.URL_QUERY,
'url.full',
'url.path',
'url.query',
'event.name',
'agent_session_id',
'do_not_scrub',
Expand Down
6 changes: 2 additions & 4 deletions logfire/_internal/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
TracerProvider as SDKTracerProvider,
)
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import Link, NonRecordingSpan, Span, SpanContext, SpanKind, Tracer, TracerProvider
from opentelemetry.trace.propagation import get_current_span
from opentelemetry.trace.status import Status, StatusCode
Expand Down Expand Up @@ -104,7 +102,7 @@ def resource(self) -> Resource: # pragma: no cover
with self.lock:
if isinstance(self.provider, SDKTracerProvider):
return self.provider.resource
return Resource.create({ResourceAttributes.SERVICE_NAME: self.config.service_name})
return Resource.create({'service.name': self.config.service_name})

def force_flush(self, timeout_millis: int = 30000) -> bool:
with self.lock:
Expand Down Expand Up @@ -382,7 +380,7 @@ def record_exception(
# ignoring the passed exception.
# So we override the stacktrace attribute with the correct one.
stacktrace = ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__))
attributes[SpanAttributes.EXCEPTION_STACKTRACE] = stacktrace
attributes['exception.stacktrace'] = stacktrace

span.record_exception(exception, attributes=attributes, timestamp=timestamp, escaped=escaped)

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ classifiers = [
"Framework :: OpenTelemetry :: Instrumentations",
]
dependencies = [
"opentelemetry-sdk >= 1.21.0, < 1.34.0",
"opentelemetry-exporter-otlp-proto-http >= 1.21.0, < 1.34.0",
"opentelemetry-sdk >= 1.21.0, < 1.35.0",
"opentelemetry-exporter-otlp-proto-http >= 1.21.0, < 1.35.0",
"opentelemetry-instrumentation >= 0.41b0",
"rich >= 13.4.2",
"protobuf >= 4.23.4",
Expand Down
Loading
Loading
0