Skip to content

Commit aabf20e

Browse files
authored
Upgrade to OpenTelemetry SDK 1.34.0 (#1120)
1 parent 63237d2 commit aabf20e

20 files changed

+1906
-692
lines changed

.github/workflows/main.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ jobs:
6464
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
6565
pydantic-version: ['2.11']
6666
include:
67-
- python-version: '3.8'
68-
pydantic-version: '2.10'
6967
- python-version: '3.12'
7068
pydantic-version: '2.4'
7169
env:

logfire/_internal/config.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
5454
from opentelemetry.sdk.trace.id_generator import IdGenerator
5555
from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio, Sampler
56-
from opentelemetry.semconv.resource import ResourceAttributes
5756
from rich.console import Console
5857
from rich.prompt import Confirm, IntPrompt, Prompt
5958
from typing_extensions import Self, Unpack
@@ -574,10 +573,6 @@ def _load_configuration(
574573
self.inspect_arguments = param_manager.load_param('inspect_arguments', inspect_arguments)
575574
self.distributed_tracing = param_manager.load_param('distributed_tracing', distributed_tracing)
576575
self.ignore_no_config = param_manager.load_param('ignore_no_config')
577-
if self.inspect_arguments and sys.version_info[:2] <= (3, 8):
578-
raise LogfireConfigError(
579-
'Inspecting arguments is only supported in Python 3.9+ and only recommended in Python 3.11+.'
580-
)
581576

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

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

818813
head = self.sampling.head
819814
sampler: Sampler | None = None
@@ -843,12 +838,8 @@ def _initialize(self) -> None:
843838

844839
def add_span_processor(span_processor: SpanProcessor) -> None:
845840
main_multiprocessor.add_span_processor(span_processor)
846-
inner_span_processor = span_processor
847-
while isinstance(p := getattr(inner_span_processor, 'processor', None), SpanProcessor):
848-
inner_span_processor = p
849-
850841
has_pending = isinstance(
851-
getattr(inner_span_processor, 'span_exporter', None),
842+
getattr(span_processor, 'span_exporter', None),
852843
(TestExporter, RemovePendingSpansExporter, SimpleConsoleSpanExporter),
853844
)
854845
if has_pending:
@@ -1038,7 +1029,7 @@ def check_token():
10381029

10391030
def fix_pid(): # pragma: no cover
10401031
with handle_internal_errors:
1041-
new_resource = resource.merge(Resource({ResourceAttributes.PROCESS_PID: os.getpid()}))
1032+
new_resource = resource.merge(Resource({'process.pid': os.getpid()}))
10421033
tracer_provider._resource = new_resource # type: ignore
10431034
meter_provider._resource = new_resource # type: ignore
10441035
logger_provider._resource = new_resource # type: ignore

logfire/_internal/db_statement_summary.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import re
44
from typing import Any, Mapping
55

6-
from opentelemetry.semconv.trace import SpanAttributes
7-
86
MAX_QUERY_MESSAGE_LENGTH = 80
97

108

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

2624
db_statement = db_statement.strip()
2725
if isinstance(message, str):
28-
db_name = attributes.get(SpanAttributes.DB_NAME)
26+
db_name = attributes.get('db.name')
2927
if db_name and isinstance(db_name, str) and message.endswith(db_name):
3028
operation = message[: -len(db_name) - 1]
3129
else:

logfire/_internal/exporters/dynamic_batch.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
from __future__ import annotations
22

33
import os
4-
from typing import cast
4+
from typing import TYPE_CHECKING
55

66
from opentelemetry.sdk.environment_variables import OTEL_BSP_SCHEDULE_DELAY
7-
from opentelemetry.sdk.trace import ReadableSpan
7+
from opentelemetry.sdk.trace import ReadableSpan, Span
88
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
99

1010
from logfire._internal.exporters.wrapper import WrapperSpanProcessor
1111

12+
if TYPE_CHECKING:
13+
from opentelemetry.sdk._shared_internal import BatchProcessor
14+
1215

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

24+
processor: BatchSpanProcessor # type: ignore
25+
2126
def __init__(self, exporter: SpanExporter) -> None:
2227
self.final_delay = float(os.environ.get(OTEL_BSP_SCHEDULE_DELAY) or 500)
2328
# Start with the configured value immediately if it's less than 100ms.
@@ -28,5 +33,19 @@ def __init__(self, exporter: SpanExporter) -> None:
2833
def on_end(self, span: ReadableSpan) -> None:
2934
self.num_processed += 1
3035
if self.num_processed == 10:
31-
cast(BatchSpanProcessor, self.processor).schedule_delay_millis = self.final_delay
36+
if hasattr(self.batch_processor, '_schedule_delay_millis'):
37+
self.batch_processor._schedule_delay = self.final_delay / 1e3 # type: ignore
38+
else: # pragma: no cover
39+
self.processor.schedule_delay_millis = self.final_delay # type: ignore
3240
super().on_end(span)
41+
42+
@property
43+
def batch_processor(self) -> BatchSpanProcessor | BatchProcessor[Span]:
44+
return getattr(self.processor, '_batch_processor', self.processor)
45+
46+
@property
47+
def span_exporter(self) -> SpanExporter:
48+
if isinstance(self.batch_processor, BatchSpanProcessor): # pragma: no cover
49+
return self.batch_processor.span_exporter # type: ignore
50+
else:
51+
return self.batch_processor._exporter # type: ignore

logfire/_internal/exporters/processor_wrapper.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from opentelemetry import context
1010
from opentelemetry.sdk.trace import ReadableSpan, Span
1111
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
12-
from opentelemetry.semconv.trace import SpanAttributes
1312
from opentelemetry.trace import SpanKind, Status, StatusCode
1413

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

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

199-
method = attributes.get(SpanAttributes.HTTP_METHOD)
200-
route = attributes.get(SpanAttributes.HTTP_ROUTE)
201-
target = attributes.get(SpanAttributes.HTTP_TARGET)
202-
url: Any = attributes.get(SpanAttributes.HTTP_URL)
198+
method = attributes.get('http.method')
199+
route = attributes.get('http.route')
200+
target = attributes.get('http.target')
201+
url: Any = attributes.get('http.url')
203202
if not (method or route or target or url):
204203
return
205204

206205
if not target and url and isinstance(url, str):
207206
try:
208207
target = urlparse(url).path
209-
span['attributes'] = attributes = {**attributes, SpanAttributes.HTTP_TARGET: target}
208+
span['attributes'] = attributes = {**attributes, 'http.target': target}
210209
except Exception: # pragma: no cover
211210
pass
212211

@@ -224,9 +223,7 @@ def _tweak_http_spans(span: ReadableSpanDict):
224223
if span['kind'] == SpanKind.CLIENT:
225224
# For outgoing requests, we also want the domain, not just the path.
226225
server_name: Any = (
227-
attributes.get(SpanAttributes.SERVER_ADDRESS)
228-
or attributes.get(SpanAttributes.HTTP_SERVER_NAME)
229-
or attributes.get(SpanAttributes.HTTP_HOST)
226+
attributes.get('server.address') or attributes.get('http.server_name') or attributes.get('http.host')
230227
)
231228
if not server_name:
232229
try:

logfire/_internal/exporters/test.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
from opentelemetry.sdk._logs.export import InMemoryLogExporter
1818
from opentelemetry.sdk.trace import Event, ReadableSpan
1919
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
20-
from opentelemetry.semconv.resource import ResourceAttributes
21-
from opentelemetry.semconv.trace import SpanAttributes
2220

2321
from ..constants import ATTRIBUTES_SPAN_TYPE_KEY
2422

@@ -82,15 +80,13 @@ def build_event(event: Event) -> dict[str, Any]:
8280
res: dict[str, Any] = {'name': event.name, 'timestamp': event.timestamp}
8381
if event.attributes: # pragma: no branch
8482
res['attributes'] = attributes = dict(event.attributes)
85-
if SpanAttributes.EXCEPTION_STACKTRACE in attributes:
83+
if 'exception.stacktrace' in attributes:
8684
last_line = next( # pragma: no branch
8785
line.strip()
88-
for line in reversed(
89-
cast(str, event.attributes[SpanAttributes.EXCEPTION_STACKTRACE]).split('\n')
90-
)
86+
for line in reversed(cast(str, event.attributes['exception.stacktrace']).split('\n'))
9187
if line.strip()
9288
)
93-
attributes[SpanAttributes.EXCEPTION_STACKTRACE] = last_line
89+
attributes['exception.stacktrace'] = last_line
9490
return res
9591

9692
def build_instrumentation_scope(span: ReadableSpan) -> dict[str, Any]:
@@ -148,10 +144,10 @@ def process_attribute(
148144
if name == 'code.function':
149145
if sys.version_info >= (3, 11) and strip_function_qualname:
150146
return value.split('.')[-1]
151-
if name == ResourceAttributes.PROCESS_PID:
147+
if name == 'process.pid':
152148
assert value == os.getpid()
153149
return 1234
154-
if name == ResourceAttributes.SERVICE_INSTANCE_ID:
150+
if name == 'service.instance.id':
155151
if re.match(r'^[0-9a-f]{32}$', value):
156152
return '0' * 32
157153
if parse_json_attributes and isinstance(value, str) and (value.startswith('{') or value.startswith('[')):

logfire/_internal/integrations/fastapi.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from fastapi import BackgroundTasks, FastAPI
1212
from fastapi.routing import APIRoute, APIWebSocketRoute, Mount
1313
from fastapi.security import SecurityScopes
14-
from opentelemetry.semconv.trace import SpanAttributes
1514
from opentelemetry.trace import Span
1615
from starlette.requests import Request
1716
from starlette.responses import Response
@@ -78,7 +77,7 @@ def instrument_fastapi(
7877
'meter_provider': logfire_instance.config.get_meter_provider(),
7978
**opentelemetry_kwargs,
8079
}
81-
FastAPIInstrumentor.instrument_app( # type: ignore
80+
FastAPIInstrumentor.instrument_app(
8281
app,
8382
excluded_urls=excluded_urls,
8483
server_request_hook=_server_request_hook(opentelemetry_kwargs.pop('server_request_hook', None)),
@@ -170,10 +169,10 @@ async def solve_dependencies(self, request: Request | WebSocket, original: Await
170169
with self.logfire_instance.span('FastAPI arguments') as span:
171170
with handle_internal_errors:
172171
if isinstance(request, Request): # pragma: no branch
173-
span.set_attribute(SpanAttributes.HTTP_METHOD, request.method)
172+
span.set_attribute('http.method', request.method)
174173
route: APIRoute | APIWebSocketRoute | None = request.scope.get('route')
175174
if route: # pragma: no branch
176-
span.set_attribute(SpanAttributes.HTTP_ROUTE, route.path)
175+
span.set_attribute('http.route', route.path)
177176
fastapi_route_attributes: dict[str, Any] = {'fastapi.route.name': route.name}
178177
if isinstance(route, APIRoute): # pragma: no branch
179178
fastapi_route_attributes['fastapi.route.operation_id'] = route.operation_id

logfire/_internal/integrations/httpx.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,17 @@ def instrument_httpx(
148148
)
149149

150150
tracer_provider = final_kwargs['tracer_provider']
151-
instrumentor.instrument_client(client, tracer_provider, request_hook, response_hook) # type: ignore[reportArgumentType]
151+
meter_provider = final_kwargs['meter_provider']
152+
client_kwargs = dict(
153+
tracer_provider=tracer_provider,
154+
request_hook=request_hook,
155+
response_hook=response_hook,
156+
)
157+
try:
158+
instrumentor.instrument_client(client, meter_provider=meter_provider, **client_kwargs)
159+
except TypeError: # pragma: no cover
160+
# This is a fallback for older versions of opentelemetry-instrumentation-httpx
161+
instrumentor.instrument_client(client, **client_kwargs)
152162

153163

154164
class LogfireHttpxInfoMixin:

logfire/_internal/scrubbing.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from opentelemetry.attributes import BoundedAttributes
1212
from opentelemetry.sdk._logs import LogRecord
1313
from opentelemetry.sdk.trace import Event
14-
from opentelemetry.semconv.trace import SpanAttributes
1514
from opentelemetry.trace import Link
1615

1716
from .constants import (
@@ -122,22 +121,21 @@ class BaseScrubber(ABC):
122121
ATTRIBUTES_SCRUBBED_KEY,
123122
RESOURCE_ATTRIBUTES_PACKAGE_VERSIONS,
124123
*STACK_INFO_KEYS,
125-
SpanAttributes.EXCEPTION_STACKTRACE,
126-
SpanAttributes.EXCEPTION_TYPE,
127-
SpanAttributes.EXCEPTION_MESSAGE,
128-
SpanAttributes.SCHEMA_URL,
129-
SpanAttributes.HTTP_METHOD,
130-
SpanAttributes.HTTP_STATUS_CODE,
131-
SpanAttributes.HTTP_SCHEME,
132-
SpanAttributes.HTTP_URL,
133-
SpanAttributes.HTTP_TARGET,
134-
SpanAttributes.HTTP_ROUTE,
135-
SpanAttributes.DB_STATEMENT,
124+
'exception.stacktrace',
125+
'exception.type',
126+
'exception.message',
127+
'http.method',
128+
'http.status_code',
129+
'http.scheme',
130+
'http.url',
131+
'http.target',
132+
'http.route',
133+
'db.statement',
136134
'db.plan',
137135
# Newer semantic conventions
138-
SpanAttributes.URL_FULL,
139-
SpanAttributes.URL_PATH,
140-
SpanAttributes.URL_QUERY,
136+
'url.full',
137+
'url.path',
138+
'url.query',
141139
'event.name',
142140
'agent_session_id',
143141
'do_not_scrub',

logfire/_internal/tracer.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
TracerProvider as SDKTracerProvider,
1919
)
2020
from opentelemetry.sdk.trace.id_generator import IdGenerator
21-
from opentelemetry.semconv.resource import ResourceAttributes
22-
from opentelemetry.semconv.trace import SpanAttributes
2321
from opentelemetry.trace import Link, NonRecordingSpan, Span, SpanContext, SpanKind, Tracer, TracerProvider
2422
from opentelemetry.trace.propagation import get_current_span
2523
from opentelemetry.trace.status import Status, StatusCode
@@ -104,7 +102,7 @@ def resource(self) -> Resource: # pragma: no cover
104102
with self.lock:
105103
if isinstance(self.provider, SDKTracerProvider):
106104
return self.provider.resource
107-
return Resource.create({ResourceAttributes.SERVICE_NAME: self.config.service_name})
105+
return Resource.create({'service.name': self.config.service_name})
108106

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

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

0 commit comments

Comments
 (0)