nvidia-nat-opentelemetry 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nat/meta/pypi.md +23 -0
- nat/plugins/opentelemetry/__init__.py +14 -0
- nat/plugins/opentelemetry/mixin/__init__.py +14 -0
- nat/plugins/opentelemetry/mixin/otlp_span_exporter_mixin.py +69 -0
- nat/plugins/opentelemetry/otel_span.py +524 -0
- nat/plugins/opentelemetry/otel_span_exporter.py +166 -0
- nat/plugins/opentelemetry/otlp_span_adapter_exporter.py +93 -0
- nat/plugins/opentelemetry/register.py +195 -0
- nat/plugins/opentelemetry/span_converter.py +228 -0
- nvidia_nat_opentelemetry-1.2.0.dist-info/METADATA +36 -0
- nvidia_nat_opentelemetry-1.2.0.dist-info/RECORD +14 -0
- nvidia_nat_opentelemetry-1.2.0.dist-info/WHEEL +5 -0
- nvidia_nat_opentelemetry-1.2.0.dist-info/entry_points.txt +2 -0
- nvidia_nat_opentelemetry-1.2.0.dist-info/top_level.txt +1 -0
nat/meta/pypi.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
19
|
+
|
|
20
|
+
from nat.plugins.opentelemetry.otel_span import OtelSpan
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OTLPSpanExporterMixin:
|
|
26
|
+
"""Mixin for OTLP span exporters.
|
|
27
|
+
|
|
28
|
+
This mixin provides OTLP-specific functionality for OpenTelemetry span exporters.
|
|
29
|
+
It handles OTLP protocol transmission using the standard OpenTelemetry OTLP HTTP exporter.
|
|
30
|
+
|
|
31
|
+
Key Features:
|
|
32
|
+
- Standard OTLP HTTP protocol support for span export
|
|
33
|
+
- Configurable endpoint and headers for authentication/routing
|
|
34
|
+
- Integration with OpenTelemetry's OTLPSpanExporter for reliable transmission
|
|
35
|
+
- Works with any OTLP-compatible collector or service
|
|
36
|
+
|
|
37
|
+
This mixin is designed to be used with OtelSpanExporter as a base class:
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
class MyOTLPExporter(OtelSpanExporter, OTLPSpanExporterMixin):
|
|
41
|
+
def __init__(self, endpoint, headers, **kwargs):
|
|
42
|
+
super().__init__(endpoint=endpoint, headers=headers, **kwargs)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, *args, endpoint: str, headers: dict[str, str] | None = None, **kwargs):
|
|
46
|
+
"""Initialize the OTLP span exporter.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
endpoint: OTLP service endpoint URL.
|
|
50
|
+
headers: HTTP headers for authentication and metadata.
|
|
51
|
+
"""
|
|
52
|
+
# Initialize exporter before super().__init__() to ensure it's available
|
|
53
|
+
# if parent class initialization potentially calls export_otel_spans()
|
|
54
|
+
self._exporter = OTLPSpanExporter(endpoint=endpoint, headers=headers)
|
|
55
|
+
super().__init__(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
async def export_otel_spans(self, spans: list[OtelSpan]) -> None:
|
|
58
|
+
"""Export a list of OtelSpans using the OTLP exporter.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
spans (list[OtelSpan]): The list of spans to export.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
Exception: If there's an error during span export (logged but not re-raised).
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
self._exporter.export(spans) # type: ignore[arg-type]
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error("Error exporting spans: %s", e, exc_info=True)
|
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
import traceback
|
|
20
|
+
import uuid
|
|
21
|
+
from collections.abc import Sequence
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from opentelemetry import trace as trace_api
|
|
26
|
+
from opentelemetry.sdk import util
|
|
27
|
+
from opentelemetry.sdk.resources import Resource
|
|
28
|
+
from opentelemetry.sdk.trace import Event
|
|
29
|
+
from opentelemetry.sdk.trace import InstrumentationScope
|
|
30
|
+
from opentelemetry.trace import Context
|
|
31
|
+
from opentelemetry.trace import Link
|
|
32
|
+
from opentelemetry.trace import SpanContext
|
|
33
|
+
from opentelemetry.trace import SpanKind
|
|
34
|
+
from opentelemetry.trace import Status
|
|
35
|
+
from opentelemetry.trace import StatusCode
|
|
36
|
+
from opentelemetry.trace import TraceFlags
|
|
37
|
+
from opentelemetry.trace.span import Span
|
|
38
|
+
from opentelemetry.util import types
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MimeTypes(Enum):
|
|
44
|
+
"""Mime types for the span."""
|
|
45
|
+
TEXT = "text/plain"
|
|
46
|
+
JSON = "application/json"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OtelSpan(Span): # pylint: disable=too-many-public-methods
|
|
50
|
+
"""A manually created OpenTelemetry span.
|
|
51
|
+
|
|
52
|
+
This class is a wrapper around the OpenTelemetry Span class.
|
|
53
|
+
It provides a more convenient interface for creating and manipulating spans.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name (str): The name of the span.
|
|
57
|
+
context (Context | SpanContext | None): The context of the span.
|
|
58
|
+
parent (Span | None): The parent span.
|
|
59
|
+
attributes (dict[str, Any] | None): The attributes of the span.
|
|
60
|
+
events (list | None): The events of the span.
|
|
61
|
+
links (list | None): The links of the span.
|
|
62
|
+
kind (int | None): The kind of the span.
|
|
63
|
+
start_time (int | None): The start time of the span in nanoseconds.
|
|
64
|
+
end_time (int | None): The end time of the span in nanoseconds.
|
|
65
|
+
status (Status | None): The status of the span.
|
|
66
|
+
resource (Resource | None): The resource of the span.
|
|
67
|
+
instrumentation_scope (InstrumentationScope | None): The instrumentation scope of the span.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
name: str,
|
|
73
|
+
context: Context | SpanContext | None,
|
|
74
|
+
parent: Span | None = None,
|
|
75
|
+
attributes: dict[str, Any] | None = None,
|
|
76
|
+
events: list | None = None,
|
|
77
|
+
links: list | None = None,
|
|
78
|
+
kind: int | SpanKind | None = None,
|
|
79
|
+
start_time: int | None = None,
|
|
80
|
+
end_time: int | None = None,
|
|
81
|
+
status: Status | None = None,
|
|
82
|
+
resource: Resource | None = None,
|
|
83
|
+
instrumentation_scope: InstrumentationScope | None = None,
|
|
84
|
+
):
|
|
85
|
+
"""Initialize the OtelSpan with the specified values."""
|
|
86
|
+
self._name = name
|
|
87
|
+
# Create a new SpanContext if none provided or if Context is provided
|
|
88
|
+
if context is None or isinstance(context, Context):
|
|
89
|
+
trace_id = uuid.uuid4().int & ((1 << 128) - 1)
|
|
90
|
+
span_id = uuid.uuid4().int & ((1 << 64) - 1)
|
|
91
|
+
self._context = SpanContext(
|
|
92
|
+
trace_id=trace_id,
|
|
93
|
+
span_id=span_id,
|
|
94
|
+
is_remote=False,
|
|
95
|
+
trace_flags=TraceFlags(1), # SAMPLED
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
self._context = context
|
|
99
|
+
self._parent = parent
|
|
100
|
+
self._attributes = attributes or {}
|
|
101
|
+
self._events = events or []
|
|
102
|
+
self._links = links or []
|
|
103
|
+
self._kind = kind or SpanKind.INTERNAL
|
|
104
|
+
self._start_time = start_time or int(time.time() * 1e9) # Convert to nanoseconds
|
|
105
|
+
self._end_time = end_time
|
|
106
|
+
self._status = status or Status(StatusCode.UNSET)
|
|
107
|
+
self._ended = False
|
|
108
|
+
self._resource = resource or Resource.create()
|
|
109
|
+
self._instrumentation_scope = instrumentation_scope or InstrumentationScope("nat", "1.0.0")
|
|
110
|
+
self._dropped_attributes = 0
|
|
111
|
+
self._dropped_events = 0
|
|
112
|
+
self._dropped_links = 0
|
|
113
|
+
self._status_description = None
|
|
114
|
+
|
|
115
|
+
# Add parent span as a link if provided
|
|
116
|
+
if parent is not None:
|
|
117
|
+
parent_context = parent.get_span_context()
|
|
118
|
+
# Create a new span context that inherits the trace ID from the parent
|
|
119
|
+
self._context = SpanContext(
|
|
120
|
+
trace_id=parent_context.trace_id,
|
|
121
|
+
span_id=self._context.span_id,
|
|
122
|
+
is_remote=False,
|
|
123
|
+
trace_flags=parent_context.trace_flags,
|
|
124
|
+
trace_state=parent_context.trace_state,
|
|
125
|
+
)
|
|
126
|
+
# Create a proper link object instead of a dictionary
|
|
127
|
+
self._links.append(Link(context=parent_context, attributes={"parent.name": self._name}))
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def resource(self) -> Resource:
|
|
131
|
+
"""Get the resource associated with this span.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Resource: The resource.
|
|
135
|
+
"""
|
|
136
|
+
return self._resource
|
|
137
|
+
|
|
138
|
+
def set_resource(self, resource: Resource) -> None:
|
|
139
|
+
"""Set the resource associated with this span.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
resource (Resource): The resource to set.
|
|
143
|
+
"""
|
|
144
|
+
self._resource = resource
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def instrumentation_scope(self) -> InstrumentationScope:
|
|
148
|
+
"""Get the instrumentation scope associated with this span.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
InstrumentationScope: The instrumentation scope.
|
|
152
|
+
"""
|
|
153
|
+
return self._instrumentation_scope
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def parent(self) -> Span | None:
|
|
157
|
+
"""Get the parent span.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Span | None: The parent span.
|
|
161
|
+
"""
|
|
162
|
+
return self._parent
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def name(self) -> str:
|
|
166
|
+
"""Get the name of the span.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
str: The name of the span.
|
|
170
|
+
"""
|
|
171
|
+
return self._name
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def kind(self) -> int | SpanKind:
|
|
175
|
+
"""Get the kind of the span.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
int | SpanKind: The kind of the span.
|
|
179
|
+
"""
|
|
180
|
+
return self._kind
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def start_time(self) -> int:
|
|
184
|
+
"""Get the start time of the span in nanoseconds.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
int: The start time of the span in nanoseconds.
|
|
188
|
+
"""
|
|
189
|
+
return self._start_time
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def end_time(self) -> int | None:
|
|
193
|
+
"""Get the end time of the span in nanoseconds.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
int | None: The end time of the span in nanoseconds.
|
|
197
|
+
"""
|
|
198
|
+
return self._end_time
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def attributes(self) -> dict[str, Any]:
|
|
202
|
+
"""Get all attributes of the span.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
dict[str, Any]: The attributes of the span.
|
|
206
|
+
"""
|
|
207
|
+
return self._attributes
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def events(self) -> list:
|
|
211
|
+
"""Get all events of the span.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
list: The events of the span.
|
|
215
|
+
"""
|
|
216
|
+
return self._events
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def links(self) -> list:
|
|
220
|
+
"""Get all links of the span.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
list: The links of the span.
|
|
224
|
+
"""
|
|
225
|
+
return self._links
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def status(self) -> Status:
|
|
229
|
+
"""Get the status of the span.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Status: The status of the span.
|
|
233
|
+
"""
|
|
234
|
+
return self._status
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def dropped_attributes(self) -> int:
|
|
238
|
+
"""Get the number of dropped attributes.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
int: The number of dropped attributes.
|
|
242
|
+
"""
|
|
243
|
+
return self._dropped_attributes
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def dropped_events(self) -> int:
|
|
247
|
+
"""Get the number of dropped events.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
int: The number of dropped events.
|
|
251
|
+
"""
|
|
252
|
+
return self._dropped_events
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def dropped_links(self) -> int:
|
|
256
|
+
"""Get the number of dropped links.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
int: The number of dropped links.
|
|
260
|
+
"""
|
|
261
|
+
return self._dropped_links
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def span_id(self) -> int:
|
|
265
|
+
"""Get the span ID.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
int: The span ID.
|
|
269
|
+
"""
|
|
270
|
+
return self._context.span_id
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def trace_id(self) -> int:
|
|
274
|
+
"""Get the trace ID.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
int: The trace ID.
|
|
278
|
+
"""
|
|
279
|
+
return self._context.trace_id
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def is_remote(self) -> bool:
|
|
283
|
+
"""Get whether this span is remote.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
bool: True if the span is remote, False otherwise.
|
|
287
|
+
"""
|
|
288
|
+
return self._context.is_remote
|
|
289
|
+
|
|
290
|
+
def end(self, end_time: int | None = None) -> None:
|
|
291
|
+
"""End the span.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
end_time (int | None): The end time of the span in nanoseconds.
|
|
295
|
+
"""
|
|
296
|
+
if not self._ended:
|
|
297
|
+
self._ended = True
|
|
298
|
+
self._end_time = end_time or int(time.time() * 1e9)
|
|
299
|
+
|
|
300
|
+
def is_recording(self) -> bool:
|
|
301
|
+
"""Check if the span is recording.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
bool: True if the span is recording, False otherwise.
|
|
305
|
+
"""
|
|
306
|
+
return not self._ended
|
|
307
|
+
|
|
308
|
+
def get_span_context(self) -> SpanContext:
|
|
309
|
+
"""Get the span context.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
SpanContext: The span context.
|
|
313
|
+
"""
|
|
314
|
+
return self._context
|
|
315
|
+
|
|
316
|
+
def set_attribute(self, key: str, value: Any) -> None:
|
|
317
|
+
"""Set an attribute on the span.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
key (str): The key of the attribute.
|
|
321
|
+
value (Any): The value of the attribute.
|
|
322
|
+
"""
|
|
323
|
+
self._attributes[key] = value
|
|
324
|
+
|
|
325
|
+
def set_attributes(self, attributes: dict[str, Any]) -> None:
|
|
326
|
+
"""Set multiple attributes on the span.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
attributes (dict[str, Any]): The attributes to set.
|
|
330
|
+
"""
|
|
331
|
+
self._attributes.update(attributes)
|
|
332
|
+
|
|
333
|
+
def add_event(self, name: str, attributes: dict[str, Any] | None = None, timestamp: int | None = None) -> None:
|
|
334
|
+
"""Add an event to the span.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
name (str): The name of the event.
|
|
338
|
+
attributes (dict[str, Any] | None): The attributes of the event.
|
|
339
|
+
timestamp (int | None): The timestamp of the event in nanoseconds.
|
|
340
|
+
"""
|
|
341
|
+
if timestamp is None:
|
|
342
|
+
timestamp = int(time.time() * 1e9)
|
|
343
|
+
self._events.append({"name": name, "attributes": attributes or {}, "timestamp": timestamp})
|
|
344
|
+
|
|
345
|
+
def update_name(self, name: str) -> None:
|
|
346
|
+
"""Update the span name.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
name (str): The name to set.
|
|
350
|
+
"""
|
|
351
|
+
self._name = name
|
|
352
|
+
|
|
353
|
+
def set_status(self, status: Status, description: str | None = None) -> None:
|
|
354
|
+
"""Set the span status.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
status (Status): The status to set.
|
|
358
|
+
description (str | None): The description of the status.
|
|
359
|
+
"""
|
|
360
|
+
self._status = status
|
|
361
|
+
self._status_description = description
|
|
362
|
+
|
|
363
|
+
def get_links(self) -> list:
|
|
364
|
+
"""Get all links of the span.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
list: The links of the span.
|
|
368
|
+
"""
|
|
369
|
+
return self._links
|
|
370
|
+
|
|
371
|
+
def get_end_time(self) -> int | None:
|
|
372
|
+
"""Get the end time of the span.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
int | None: The end time of the span in nanoseconds.
|
|
376
|
+
"""
|
|
377
|
+
return self._end_time
|
|
378
|
+
|
|
379
|
+
def get_status(self) -> Status:
|
|
380
|
+
"""Get the status of the span.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Status: The status of the span.
|
|
384
|
+
"""
|
|
385
|
+
return self._status
|
|
386
|
+
|
|
387
|
+
def get_parent(self) -> Span | None:
|
|
388
|
+
"""Get the parent span.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Span | None: The parent span.
|
|
392
|
+
"""
|
|
393
|
+
return self._parent
|
|
394
|
+
|
|
395
|
+
def record_exception(self,
|
|
396
|
+
exception: Exception,
|
|
397
|
+
attributes: dict[str, Any] | None = None,
|
|
398
|
+
timestamp: int | None = None,
|
|
399
|
+
escaped: bool = False) -> None:
|
|
400
|
+
"""
|
|
401
|
+
Record an exception on the span.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
exception: The exception to record
|
|
405
|
+
attributes: Optional dictionary of attributes to add to the event
|
|
406
|
+
timestamp: Optional timestamp for the event
|
|
407
|
+
escaped: Whether the exception was escaped
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
if timestamp is None:
|
|
411
|
+
timestamp = int(time.time() * 1e9)
|
|
412
|
+
|
|
413
|
+
# Get the exception type and message
|
|
414
|
+
exc_type = type(exception).__name__
|
|
415
|
+
exc_message = str(exception)
|
|
416
|
+
|
|
417
|
+
# Get the stack trace
|
|
418
|
+
exc_traceback = traceback.format_exception(type(exception), exception, exception.__traceback__)
|
|
419
|
+
stack_trace = "".join(exc_traceback)
|
|
420
|
+
|
|
421
|
+
# Create the event attributes
|
|
422
|
+
event_attrs = {
|
|
423
|
+
"exception.type": exc_type,
|
|
424
|
+
"exception.message": exc_message,
|
|
425
|
+
"exception.stacktrace": stack_trace,
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# Add any additional attributes
|
|
429
|
+
if attributes:
|
|
430
|
+
event_attrs.update(attributes)
|
|
431
|
+
|
|
432
|
+
# Add the event to the span
|
|
433
|
+
self.add_event("exception", event_attrs)
|
|
434
|
+
|
|
435
|
+
# Set the span status to error
|
|
436
|
+
self.set_status(Status(StatusCode.ERROR, exc_message))
|
|
437
|
+
|
|
438
|
+
def copy(self) -> "OtelSpan":
|
|
439
|
+
"""
|
|
440
|
+
Create a new OtelSpan instance with the same values as this one.
|
|
441
|
+
Note that this is not a deep copy - mutable objects like attributes, events, and links
|
|
442
|
+
will be shared between the original and the copy.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
A new OtelSpan instance with the same values
|
|
446
|
+
"""
|
|
447
|
+
return OtelSpan(
|
|
448
|
+
name=self._name,
|
|
449
|
+
context=self._context,
|
|
450
|
+
parent=self._parent,
|
|
451
|
+
attributes=self._attributes.copy(),
|
|
452
|
+
events=self._events.copy(),
|
|
453
|
+
links=self._links.copy(),
|
|
454
|
+
kind=self._kind,
|
|
455
|
+
start_time=self._start_time,
|
|
456
|
+
end_time=self._end_time,
|
|
457
|
+
status=self._status,
|
|
458
|
+
resource=self._resource,
|
|
459
|
+
instrumentation_scope=self._instrumentation_scope,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
@staticmethod
|
|
463
|
+
def _format_context(context: SpanContext) -> dict[str, str]:
|
|
464
|
+
return {
|
|
465
|
+
"trace_id": f"0x{trace_api.format_trace_id(context.trace_id)}",
|
|
466
|
+
"span_id": f"0x{trace_api.format_span_id(context.span_id)}",
|
|
467
|
+
"trace_state": repr(context.trace_state),
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@staticmethod
|
|
471
|
+
def _format_attributes(attributes: types.Attributes, ) -> dict[str, Any] | None:
|
|
472
|
+
if attributes is not None and not isinstance(attributes, dict):
|
|
473
|
+
return dict(attributes)
|
|
474
|
+
return attributes
|
|
475
|
+
|
|
476
|
+
@staticmethod
|
|
477
|
+
def _format_events(events: Sequence[Event]) -> list[dict[str, Any]]:
|
|
478
|
+
return [{
|
|
479
|
+
"name": event.name,
|
|
480
|
+
"timestamp": util.ns_to_iso_str(event.timestamp),
|
|
481
|
+
"attributes": OtelSpan._format_attributes(event.attributes),
|
|
482
|
+
} for event in events]
|
|
483
|
+
|
|
484
|
+
@staticmethod
|
|
485
|
+
def _format_links(links: Sequence[trace_api.Link]) -> list[dict[str, Any]]:
|
|
486
|
+
return [{
|
|
487
|
+
"context": OtelSpan._format_context(link.context),
|
|
488
|
+
"attributes": OtelSpan._format_attributes(link.attributes),
|
|
489
|
+
} for link in links]
|
|
490
|
+
|
|
491
|
+
def to_json(self, indent: int | None = 4):
|
|
492
|
+
parent_id = None
|
|
493
|
+
if self.parent is not None:
|
|
494
|
+
parent_id = f"0x{trace_api.format_span_id(self.parent.span_id)}" # type: ignore
|
|
495
|
+
|
|
496
|
+
start_time = None
|
|
497
|
+
if self._start_time:
|
|
498
|
+
start_time = util.ns_to_iso_str(self._start_time)
|
|
499
|
+
|
|
500
|
+
end_time = None
|
|
501
|
+
if self._end_time:
|
|
502
|
+
end_time = util.ns_to_iso_str(self._end_time)
|
|
503
|
+
|
|
504
|
+
status = {
|
|
505
|
+
"status_code": str(self._status.status_code.name),
|
|
506
|
+
}
|
|
507
|
+
if self._status.description:
|
|
508
|
+
status["description"] = self._status.description
|
|
509
|
+
|
|
510
|
+
f_span = {
|
|
511
|
+
"name": self._name,
|
|
512
|
+
"context": (self._format_context(self._context) if self._context else None),
|
|
513
|
+
"kind": str(self.kind),
|
|
514
|
+
"parent_id": parent_id,
|
|
515
|
+
"start_time": start_time,
|
|
516
|
+
"end_time": end_time,
|
|
517
|
+
"status": status,
|
|
518
|
+
"attributes": self._format_attributes(self._attributes),
|
|
519
|
+
"events": self._format_events(self._events),
|
|
520
|
+
"links": self._format_links(self._links),
|
|
521
|
+
"resource": json.loads(self.resource.to_json()),
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return json.dumps(f_span, indent=indent)
|