scale-gp-beta 0.1.0a17__py3-none-any.whl → 0.1.0a19__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.
- scale_gp_beta/_version.py +1 -1
- scale_gp_beta/lib/tracing/__init__.py +4 -0
- scale_gp_beta/lib/tracing/exceptions.py +5 -0
- scale_gp_beta/lib/tracing/scope.py +104 -0
- scale_gp_beta/lib/tracing/span.py +216 -0
- scale_gp_beta/lib/tracing/trace.py +118 -0
- scale_gp_beta/lib/tracing/trace_exporter.py +87 -0
- scale_gp_beta/lib/tracing/trace_queue_manager.py +181 -0
- scale_gp_beta/lib/tracing/tracing.py +180 -0
- scale_gp_beta/lib/tracing/types.py +40 -0
- scale_gp_beta/lib/tracing/util.py +68 -0
- {scale_gp_beta-0.1.0a17.dist-info → scale_gp_beta-0.1.0a19.dist-info}/METADATA +1 -1
- {scale_gp_beta-0.1.0a17.dist-info → scale_gp_beta-0.1.0a19.dist-info}/RECORD +15 -5
- {scale_gp_beta-0.1.0a17.dist-info → scale_gp_beta-0.1.0a19.dist-info}/WHEEL +0 -0
- {scale_gp_beta-0.1.0a17.dist-info → scale_gp_beta-0.1.0a19.dist-info}/licenses/LICENSE +0 -0
scale_gp_beta/_version.py
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import contextvars
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from .span import BaseSpan
|
|
6
|
+
from .trace import BaseTrace
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_current_span: contextvars.ContextVar["BaseSpan | None"] = contextvars.ContextVar("current_span", default=None)
|
|
10
|
+
|
|
11
|
+
_current_trace: contextvars.ContextVar["BaseTrace | None"] = contextvars.ContextVar("current_trace", default=None)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Scope:
|
|
15
|
+
"""
|
|
16
|
+
Manages the currently active span and trace within a context.
|
|
17
|
+
|
|
18
|
+
This class provides methods to get, set, and reset the current `BaseSpan`
|
|
19
|
+
and `BaseTrace` using `contextvars`. This allows for context-local
|
|
20
|
+
storage of the active span and trace.
|
|
21
|
+
|
|
22
|
+
Both traces and spans are managed in a way that allows for nesting.
|
|
23
|
+
While traces are not typically expected to be nested, this class handles
|
|
24
|
+
such scenarios gracefully by managing them with context variables, similar
|
|
25
|
+
to how spans are managed.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def get_current_span(cls) -> Optional["BaseSpan"]:
|
|
30
|
+
"""
|
|
31
|
+
Retrieves the currently active span from the context.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Optional["BaseSpan"]: The currently active span, or None if no
|
|
35
|
+
span is active in the current context.
|
|
36
|
+
"""
|
|
37
|
+
return _current_span.get()
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def set_current_span(cls, span: Optional["BaseSpan"]) -> contextvars.Token[Optional["BaseSpan"]]:
|
|
41
|
+
"""
|
|
42
|
+
Sets the currently active span in the context.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
span: The span to set as the current active span. Can be None
|
|
46
|
+
to indicate no active span.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
contextvars.Token[Optional["BaseSpan"]]: A token that can be used
|
|
50
|
+
to reset the context variable
|
|
51
|
+
to its previous state.
|
|
52
|
+
"""
|
|
53
|
+
return _current_span.set(span)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def reset_current_span(cls, token: contextvars.Token[Optional["BaseSpan"]]) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Resets the current span in the context to its previous state.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
token: The token returned by a previous call to `set_current_span`.
|
|
62
|
+
This token is used to restore the context variable to the
|
|
63
|
+
value it had before the `set` call that D_GENERATED the token.
|
|
64
|
+
"""
|
|
65
|
+
_current_span.reset(token)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_current_trace(cls) -> Optional["BaseTrace"]:
|
|
69
|
+
"""
|
|
70
|
+
Retrieves the currently active trace from the context.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Optional["BaseTrace"]: The currently active trace, or None if no
|
|
74
|
+
trace is active in the current context.
|
|
75
|
+
"""
|
|
76
|
+
return _current_trace.get()
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def set_current_trace(cls, trace: Optional["BaseTrace"]) -> contextvars.Token[Optional["BaseTrace"]]:
|
|
80
|
+
"""
|
|
81
|
+
Sets the currently active trace in the context.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
trace: The trace to set as the current active trace. Can be None
|
|
85
|
+
to indicate no active trace.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
contextvars.Token[Optional["BaseTrace"]]: A token that can be used
|
|
89
|
+
to reset the context variable
|
|
90
|
+
to its previous state.
|
|
91
|
+
"""
|
|
92
|
+
return _current_trace.set(trace)
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def reset_current_trace(cls, token: contextvars.Token[Optional["BaseTrace"]]) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Resets the current trace in the context to its previous state.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
token: The token returned by a previous call to `set_current_trace`.
|
|
101
|
+
This token is used to restore the context variable to the
|
|
102
|
+
value it had before the `set` call that D_GENERATED the token.
|
|
103
|
+
"""
|
|
104
|
+
_current_trace.reset(token)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Type, Optional
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from scale_gp_beta.types.span_upsert_batch_params import Item as SpanCreateRequest
|
|
8
|
+
|
|
9
|
+
from .util import iso_timestamp, generate_span_id
|
|
10
|
+
from .scope import Scope
|
|
11
|
+
from .types import SpanTypeLiterals, SpanStatusLiterals
|
|
12
|
+
from .exceptions import ParamsCreationError
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
import contextvars
|
|
16
|
+
from types import TracebackType
|
|
17
|
+
|
|
18
|
+
from .trace_queue_manager import TraceQueueManager
|
|
19
|
+
|
|
20
|
+
log: logging.Logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseSpan:
|
|
24
|
+
"""Base class for all span types, providing common attributes and context management.
|
|
25
|
+
|
|
26
|
+
A span represents a single unit of work or operation within a trace. This base
|
|
27
|
+
class defines the core interface and properties for spans, such as name, IDs,
|
|
28
|
+
timestamps, and methods for starting and ending the span's lifecycle.
|
|
29
|
+
|
|
30
|
+
It is intended to be subclassed by concrete span implementations like `Span`
|
|
31
|
+
(for active tracing) and `NoOpSpan` (for when tracing is disabled).
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
name (str): The human-readable name of the span.
|
|
35
|
+
trace_id (str): The ID of the trace this span belongs to.
|
|
36
|
+
span_id (str): The unique ID of this span.
|
|
37
|
+
parent_span_id (Optional[str]): The ID of the parent span, if this is a child span.
|
|
38
|
+
start_time (Optional[str]): ISO 8601 timestamp of when the span started.
|
|
39
|
+
Set by the `start()` method.
|
|
40
|
+
end_time (Optional[str]): ISO 8601 timestamp of when the span ended.
|
|
41
|
+
Set by the `end()` method.
|
|
42
|
+
input (Optional[dict[str, Any]]): Input data or parameters for the span's operation.
|
|
43
|
+
output (Optional[dict[str, Any]]): Output data or results from the span's operation.
|
|
44
|
+
metadata (Optional[dict[str, Any]]): Additional arbitrary key-value metadata for the span.
|
|
45
|
+
_contextvar_token (Optional[contextvars.Token]): Token for managing the span's presence
|
|
46
|
+
in the current execution context.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
name: str,
|
|
52
|
+
trace_id: Optional[str] = None,
|
|
53
|
+
queue_manager: Optional[TraceQueueManager] = None,
|
|
54
|
+
span_id: Optional[str] = None,
|
|
55
|
+
parent_span_id: Optional[str] = None,
|
|
56
|
+
input: Optional[dict[str, Any]] = None,
|
|
57
|
+
output: Optional[dict[str, Any]] = None,
|
|
58
|
+
metadata: Optional[dict[str, Any]] = None,
|
|
59
|
+
span_type: SpanTypeLiterals = "STANDALONE"
|
|
60
|
+
):
|
|
61
|
+
self.name = name
|
|
62
|
+
self.trace_id = trace_id or "no_trace_id"
|
|
63
|
+
self.span_id: str = span_id or generate_span_id()
|
|
64
|
+
self.parent_span_id = parent_span_id
|
|
65
|
+
self.start_time: Optional[str] = None
|
|
66
|
+
self.end_time: Optional[str] = None
|
|
67
|
+
self.input = input
|
|
68
|
+
self.output = output
|
|
69
|
+
self.metadata = metadata
|
|
70
|
+
self.span_type: SpanTypeLiterals = span_type
|
|
71
|
+
self.status: SpanStatusLiterals = "SUCCESS"
|
|
72
|
+
self._queue_manager = queue_manager
|
|
73
|
+
|
|
74
|
+
self._contextvar_token: Optional[contextvars.Token[Optional[BaseSpan]]] = None
|
|
75
|
+
|
|
76
|
+
def start(self) -> None:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def end(self) -> None:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def __enter__(self) -> BaseSpan:
|
|
83
|
+
self.start()
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def __exit__(
|
|
87
|
+
self,
|
|
88
|
+
exc_type: Optional[Type[BaseException]],
|
|
89
|
+
exc_val: Optional[BaseException],
|
|
90
|
+
exc_tb: Optional[TracebackType]
|
|
91
|
+
) -> None:
|
|
92
|
+
# Naively record details in metadata for now, note that error capture only supported in context manager
|
|
93
|
+
# TODO: support error observations when using direct span.start() and span.end()
|
|
94
|
+
if exc_type is not None:
|
|
95
|
+
if self.metadata is None:
|
|
96
|
+
self.metadata = {}
|
|
97
|
+
self.metadata["error"] = True
|
|
98
|
+
self.metadata["error.type"] = exc_type.__name__
|
|
99
|
+
self.metadata["error.message"] = str(exc_val)
|
|
100
|
+
self.status = "ERROR"
|
|
101
|
+
self.end()
|
|
102
|
+
|
|
103
|
+
def to_request_params(self) -> SpanCreateRequest:
|
|
104
|
+
if self.start_time is None:
|
|
105
|
+
raise ParamsCreationError("No start time specified")
|
|
106
|
+
|
|
107
|
+
request_data = SpanCreateRequest(
|
|
108
|
+
name=self.name,
|
|
109
|
+
id=self.span_id,
|
|
110
|
+
trace_id=self.trace_id,
|
|
111
|
+
start_timestamp=self.start_time,
|
|
112
|
+
input=self.input or {},
|
|
113
|
+
output=self.output or {},
|
|
114
|
+
metadata=self.metadata or {},
|
|
115
|
+
status=self.status,
|
|
116
|
+
type=self.span_type
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if self.end_time is not None:
|
|
120
|
+
request_data["end_timestamp"] = self.end_time
|
|
121
|
+
|
|
122
|
+
# parent_span_id is optional (root spans)
|
|
123
|
+
if self.parent_span_id is not None:
|
|
124
|
+
request_data["parent_id"] = self.parent_span_id
|
|
125
|
+
|
|
126
|
+
return request_data
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class NoOpSpan(BaseSpan):
|
|
130
|
+
@override
|
|
131
|
+
def start(self) -> None:
|
|
132
|
+
if self.start_time is not None:
|
|
133
|
+
log.warning(f"Span {self.name}: {self.span_id} has already started at {self.start_time}")
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
self.start_time = iso_timestamp()
|
|
137
|
+
self._contextvar_token = Scope.set_current_span(self)
|
|
138
|
+
|
|
139
|
+
@override
|
|
140
|
+
def end(self) -> None:
|
|
141
|
+
if self.end_time is not None:
|
|
142
|
+
log.warning(f"Span {self.name}: {self.span_id} has already ended at {self.end_time}")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
if self._contextvar_token is None:
|
|
146
|
+
log.warning(f"Span {self.name}: {self.span_id} has not started yet.")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
self.end_time = iso_timestamp()
|
|
150
|
+
Scope.reset_current_span(self._contextvar_token)
|
|
151
|
+
self._contextvar_token = None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Span(BaseSpan):
|
|
155
|
+
"""An operational span implementation that records and reports tracing data.
|
|
156
|
+
|
|
157
|
+
`Span` instances represent actual units of work that are part of an active trace.
|
|
158
|
+
They record timestamps, manage their context in `Scope`, and interact with the
|
|
159
|
+
`TraceQueueManager` to report their start and end events for later export.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
name: str,
|
|
165
|
+
trace_id: str,
|
|
166
|
+
queue_manager: TraceQueueManager,
|
|
167
|
+
span_id: Optional[str] = None,
|
|
168
|
+
parent_span_id: Optional[str] = None,
|
|
169
|
+
input: Optional[dict[str, Any]] = None,
|
|
170
|
+
output: Optional[dict[str, Any]] = None,
|
|
171
|
+
metadata: Optional[dict[str, Any]] = None,
|
|
172
|
+
span_type: SpanTypeLiterals = "STANDALONE",
|
|
173
|
+
):
|
|
174
|
+
super().__init__(name, trace_id, queue_manager, span_id, parent_span_id, input, output, metadata, span_type)
|
|
175
|
+
self._queue_manager: TraceQueueManager = queue_manager
|
|
176
|
+
self.trace_id: str = trace_id
|
|
177
|
+
|
|
178
|
+
@override
|
|
179
|
+
def start(self) -> None:
|
|
180
|
+
"""Starts the operational Span.
|
|
181
|
+
|
|
182
|
+
Sets the `start_time`, reports the span start to the `TraceQueueManager`
|
|
183
|
+
, and registers this span as the current span.
|
|
184
|
+
"""
|
|
185
|
+
if self.start_time is not None:
|
|
186
|
+
log.warning(f"Span {self.name}: {self.span_id} has already started at {self.start_time}")
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
self.start_time = iso_timestamp()
|
|
190
|
+
self._queue_manager.report_span_start(self)
|
|
191
|
+
self._contextvar_token = Scope.set_current_span(self)
|
|
192
|
+
|
|
193
|
+
@override
|
|
194
|
+
def end(self) -> None:
|
|
195
|
+
"""Ends the operational Span.
|
|
196
|
+
|
|
197
|
+
Sets the `end_time`, reports the span end (with its complete data) to the
|
|
198
|
+
`TraceQueueManager` for queuing and export, and resets this span from the
|
|
199
|
+
`Scope`.
|
|
200
|
+
"""
|
|
201
|
+
if self.end_time is not None:
|
|
202
|
+
log.warning(f"Span {self.name}: {self.span_id} has already ended at {self.end_time}")
|
|
203
|
+
return
|
|
204
|
+
if self._contextvar_token is None:
|
|
205
|
+
log.warning(
|
|
206
|
+
(
|
|
207
|
+
f"Span {self.name}: {self.span_id} attempting to end without a valid context token. "
|
|
208
|
+
"Was start() called and completed successfully?"
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
self.end_time = iso_timestamp()
|
|
214
|
+
self._queue_manager.report_span_end(self)
|
|
215
|
+
Scope.reset_current_span(self._contextvar_token)
|
|
216
|
+
self._contextvar_token = None
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import contextvars
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typing import Dict, Type, Optional
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from .span import Span, NoOpSpan
|
|
8
|
+
from .util import generate_trace_id
|
|
9
|
+
from .scope import Scope
|
|
10
|
+
from .trace_queue_manager import TraceQueueManager
|
|
11
|
+
|
|
12
|
+
log: logging.Logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseTrace:
|
|
16
|
+
def __init__(self, queue_manager: Optional[TraceQueueManager], trace_id: Optional[str] = None):
|
|
17
|
+
self.trace_id = trace_id or generate_trace_id()
|
|
18
|
+
self.queue_manager = queue_manager
|
|
19
|
+
|
|
20
|
+
self._in_progress = False
|
|
21
|
+
self._contextvar_token: Optional[contextvars.Token[Optional[BaseTrace]]] = None
|
|
22
|
+
|
|
23
|
+
def start(self) -> None:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def end(self) -> None:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def __enter__(self) -> "BaseTrace":
|
|
30
|
+
self.start()
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def __exit__(
|
|
34
|
+
self,
|
|
35
|
+
exc_type: Optional[Type[BaseException]] = None,
|
|
36
|
+
exc_value: Optional[BaseException] = None,
|
|
37
|
+
traceback: Optional[TracebackType] = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
self.end()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NoOpTrace(BaseTrace):
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
name: str,
|
|
46
|
+
queue_manager: Optional[TraceQueueManager] = None,
|
|
47
|
+
trace_id: Optional[str] = None,
|
|
48
|
+
root_span_id: Optional[str] = None,
|
|
49
|
+
metadata: Optional[Dict[str, Optional[str]]] = None,
|
|
50
|
+
):
|
|
51
|
+
super().__init__(queue_manager, trace_id)
|
|
52
|
+
|
|
53
|
+
self.root_span = NoOpSpan(
|
|
54
|
+
name=name,
|
|
55
|
+
span_id=root_span_id,
|
|
56
|
+
trace_id=self.trace_id,
|
|
57
|
+
queue_manager=queue_manager,
|
|
58
|
+
metadata=metadata,
|
|
59
|
+
span_type="TRACER"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
def start(self) -> None:
|
|
64
|
+
self.root_span.start()
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def end(self) -> None:
|
|
68
|
+
self.root_span.end()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Trace(BaseTrace):
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
name: str,
|
|
75
|
+
queue_manager: TraceQueueManager,
|
|
76
|
+
trace_id: Optional[str] = None,
|
|
77
|
+
root_span_id: Optional[str] = None,
|
|
78
|
+
metadata: Optional[Dict[str, Optional[str]]] = None,
|
|
79
|
+
):
|
|
80
|
+
super().__init__(queue_manager, trace_id)
|
|
81
|
+
self.queue_manager: TraceQueueManager = queue_manager
|
|
82
|
+
|
|
83
|
+
self.root_span = Span(
|
|
84
|
+
name=name,
|
|
85
|
+
span_id=root_span_id,
|
|
86
|
+
trace_id=self.trace_id,
|
|
87
|
+
queue_manager=queue_manager,
|
|
88
|
+
metadata=metadata,
|
|
89
|
+
span_type="TRACER"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
def start(self) -> None:
|
|
94
|
+
if self._in_progress:
|
|
95
|
+
log.warning(f"Trace already started: {self.trace_id}")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
self._in_progress = True
|
|
99
|
+
self.queue_manager.report_trace_start(self) # no-op
|
|
100
|
+
self._contextvar_token = Scope.set_current_trace(self)
|
|
101
|
+
|
|
102
|
+
self.root_span.start()
|
|
103
|
+
|
|
104
|
+
@override
|
|
105
|
+
def end(self) -> None:
|
|
106
|
+
if not self._in_progress:
|
|
107
|
+
log.warning(f"Ending trace which is not active: {self.trace_id}")
|
|
108
|
+
return
|
|
109
|
+
if self._contextvar_token is None:
|
|
110
|
+
log.warning(f"Ending trace which is not active: {self.trace_id}, contextvar_token not set")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
self._in_progress = False
|
|
114
|
+
self.queue_manager.report_trace_end(self) # no-op
|
|
115
|
+
Scope.reset_current_trace(self._contextvar_token)
|
|
116
|
+
self._contextvar_token = None
|
|
117
|
+
|
|
118
|
+
self.root_span.end()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
from queue import Empty, Queue
|
|
4
|
+
from typing import TYPE_CHECKING, List
|
|
5
|
+
from typing_extensions import TypeAlias
|
|
6
|
+
|
|
7
|
+
from scale_gp_beta import SGPClient
|
|
8
|
+
from scale_gp_beta._exceptions import APIError
|
|
9
|
+
from scale_gp_beta.types.span_upsert_batch_params import Item as SpanCreateRequest
|
|
10
|
+
|
|
11
|
+
from .exceptions import ParamsCreationError
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .tracing import Span
|
|
15
|
+
|
|
16
|
+
INITIAL_BACKOFF = 0.4
|
|
17
|
+
MAX_BACKOFF = 20
|
|
18
|
+
|
|
19
|
+
log: logging.Logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
SpanRequestBatch: TypeAlias = List[SpanCreateRequest]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TraceExporter:
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
max_batch_size: int,
|
|
28
|
+
max_retries: int,
|
|
29
|
+
backoff: float = INITIAL_BACKOFF,
|
|
30
|
+
max_backoff: float = MAX_BACKOFF,
|
|
31
|
+
):
|
|
32
|
+
self.max_batch_size = max_batch_size
|
|
33
|
+
self.max_retries = max_retries
|
|
34
|
+
self.backoff = backoff
|
|
35
|
+
self.max_backoff = max_backoff
|
|
36
|
+
|
|
37
|
+
def export(self, client: SGPClient, queue: "Queue[Span]") -> None:
|
|
38
|
+
# this is also thread safe, two threads can call this method with the same queue at once
|
|
39
|
+
# the ordering of the requests might be randomly split between the two threads, but they should all be picked up
|
|
40
|
+
batches: list[SpanRequestBatch] = self._create_batches(queue)
|
|
41
|
+
|
|
42
|
+
log.info(f"Exporting {len(batches)} span batches")
|
|
43
|
+
|
|
44
|
+
for batch in batches:
|
|
45
|
+
self._export_batch(batch, client)
|
|
46
|
+
|
|
47
|
+
def _export_batch(self, batch: SpanRequestBatch, client: SGPClient) -> None:
|
|
48
|
+
attempts_remaining = self.max_retries
|
|
49
|
+
backoff_delay = self.backoff
|
|
50
|
+
while attempts_remaining > 0:
|
|
51
|
+
attempts_remaining -= 1
|
|
52
|
+
try:
|
|
53
|
+
client.spans.upsert_batch(items=batch)
|
|
54
|
+
return
|
|
55
|
+
except APIError as e:
|
|
56
|
+
log.warning(
|
|
57
|
+
f"API error occurred while exporting batch: {e.message}, attempts remaining: {attempts_remaining}"
|
|
58
|
+
)
|
|
59
|
+
if attempts_remaining == 0:
|
|
60
|
+
continue
|
|
61
|
+
time.sleep(backoff_delay)
|
|
62
|
+
backoff_delay = min(backoff_delay * 2, self.max_backoff)
|
|
63
|
+
|
|
64
|
+
log.error(f"Failed to export span batch after {self.max_retries} attempts, dropping...")
|
|
65
|
+
|
|
66
|
+
def _create_batches(self, queue: "Queue[Span]") -> "list[SpanRequestBatch]":
|
|
67
|
+
"""Drain the queue and return a list of batches"""
|
|
68
|
+
batches: list[SpanRequestBatch] = []
|
|
69
|
+
|
|
70
|
+
while True:
|
|
71
|
+
span_batch: SpanRequestBatch = []
|
|
72
|
+
|
|
73
|
+
while len(span_batch) < self.max_batch_size and queue.qsize() > 0:
|
|
74
|
+
try:
|
|
75
|
+
span: "Span" = queue.get_nowait()
|
|
76
|
+
span_request = span.to_request_params()
|
|
77
|
+
span_batch.append(span_request)
|
|
78
|
+
except Empty:
|
|
79
|
+
break
|
|
80
|
+
except ParamsCreationError as e:
|
|
81
|
+
log.warning(f"ParamsCreationError: {e}\ndropping...")
|
|
82
|
+
|
|
83
|
+
if not span_batch:
|
|
84
|
+
break
|
|
85
|
+
batches.append(span_batch)
|
|
86
|
+
|
|
87
|
+
return batches
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import queue
|
|
3
|
+
import atexit
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
|
|
8
|
+
from scale_gp_beta import SGPClient, SGPClientError
|
|
9
|
+
|
|
10
|
+
from .util import configure, is_disabled
|
|
11
|
+
from .trace_exporter import TraceExporter
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .span import Span
|
|
15
|
+
from .trace import Trace
|
|
16
|
+
|
|
17
|
+
# configurable by env vars?
|
|
18
|
+
DEFAULT_MAX_QUEUE_SIZE = 4_000
|
|
19
|
+
DEFAULT_TRIGGER_QUEUE_SIZE = 200
|
|
20
|
+
DEFAULT_TRIGGER_CADENCE = 4.0
|
|
21
|
+
DEFAULT_MAX_BATCH_SIZE = 50
|
|
22
|
+
DEFAULT_RETRIES = 4
|
|
23
|
+
|
|
24
|
+
WORKER_SLEEP_SECONDS = 0.1
|
|
25
|
+
|
|
26
|
+
log: logging.Logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TraceQueueManager:
|
|
30
|
+
"""Manage trace and spans queue
|
|
31
|
+
Store spans in-memory until the threshold has been reached then flush to server.
|
|
32
|
+
|
|
33
|
+
Optionally provide a client, if unprovided we will attempt to create a default client.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
client: Optional[SGPClient] = None,
|
|
39
|
+
max_queue_size: int = DEFAULT_MAX_QUEUE_SIZE,
|
|
40
|
+
max_batch_size: int = DEFAULT_MAX_BATCH_SIZE,
|
|
41
|
+
trigger_queue_size: int = DEFAULT_TRIGGER_QUEUE_SIZE,
|
|
42
|
+
trigger_cadence: float = DEFAULT_TRIGGER_CADENCE,
|
|
43
|
+
retries: int = DEFAULT_RETRIES,
|
|
44
|
+
):
|
|
45
|
+
self._client = client
|
|
46
|
+
self._attempted_local_client_creation = False
|
|
47
|
+
self._trigger_queue_size = trigger_queue_size
|
|
48
|
+
self._trigger_cadence = trigger_cadence
|
|
49
|
+
|
|
50
|
+
self._reset_trigger_time()
|
|
51
|
+
|
|
52
|
+
self._exporter = TraceExporter(max_batch_size, retries)
|
|
53
|
+
|
|
54
|
+
self._shutdown_event = threading.Event()
|
|
55
|
+
self._queue: queue.Queue[Span] = queue.Queue(maxsize=max_queue_size)
|
|
56
|
+
|
|
57
|
+
if not is_disabled():
|
|
58
|
+
self._worker = threading.Thread(daemon=True, target=self._run)
|
|
59
|
+
self._worker.start()
|
|
60
|
+
|
|
61
|
+
# ensure the thread joins on exit
|
|
62
|
+
atexit.register(self.shutdown)
|
|
63
|
+
|
|
64
|
+
def register_client(self, client: SGPClient) -> None:
|
|
65
|
+
log.info("Registering client")
|
|
66
|
+
self._client = client
|
|
67
|
+
|
|
68
|
+
def shutdown(self, timeout: Optional[float] = None) -> None:
|
|
69
|
+
if is_disabled():
|
|
70
|
+
log.debug("No worker to shutdown")
|
|
71
|
+
return
|
|
72
|
+
log.info(f"Shutting down trace queue manager, joining worker thread with timeout {timeout}")
|
|
73
|
+
self._shutdown_event.set()
|
|
74
|
+
self._worker.join(timeout=timeout)
|
|
75
|
+
log.info("Shutdown complete")
|
|
76
|
+
|
|
77
|
+
def report_span_start(self, span: "Span") -> None:
|
|
78
|
+
# TODO: support making this optional. Current backend requires us to send span starts
|
|
79
|
+
try:
|
|
80
|
+
self._queue.put_nowait(span)
|
|
81
|
+
except queue.Full:
|
|
82
|
+
log.warning(f"Queue full, ignoring span {span.span_id}")
|
|
83
|
+
|
|
84
|
+
def report_span_end(self, span: "Span") -> None:
|
|
85
|
+
try:
|
|
86
|
+
self._queue.put_nowait(span)
|
|
87
|
+
except queue.Full:
|
|
88
|
+
log.warning(f"Queue full, ignoring span {span.span_id}")
|
|
89
|
+
|
|
90
|
+
def report_trace_start(self, trace: "Trace") -> None:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def report_trace_end(self, trace: "Trace") -> None:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
def flush_queue(self) -> None:
|
|
97
|
+
self._export()
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def client(self) -> Optional[SGPClient]:
|
|
101
|
+
"""
|
|
102
|
+
Use client provided client on init if available, otherwise attempt once to create a default one.
|
|
103
|
+
:return: SGPClient
|
|
104
|
+
"""
|
|
105
|
+
if self._client is not None:
|
|
106
|
+
return self._client
|
|
107
|
+
if self._attempted_local_client_creation:
|
|
108
|
+
# Already tried and failed to create a client
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
log.info("Tracing queue manager not initialized, attempting to create a default one")
|
|
112
|
+
try:
|
|
113
|
+
self.register_client(SGPClient())
|
|
114
|
+
except SGPClientError:
|
|
115
|
+
log.warning(
|
|
116
|
+
f"Failed to create SGPClient for tracing queue manager {self}, ignoring traces. Please initialize with a working client."
|
|
117
|
+
)
|
|
118
|
+
finally:
|
|
119
|
+
self._attempted_local_client_creation = True
|
|
120
|
+
return self._client
|
|
121
|
+
|
|
122
|
+
def _run(self) -> None:
|
|
123
|
+
# daemon worker loop
|
|
124
|
+
while not self._shutdown_event.is_set():
|
|
125
|
+
current_time = time.time()
|
|
126
|
+
queue_size = self._queue.qsize()
|
|
127
|
+
|
|
128
|
+
if queue_size >= self._trigger_queue_size or current_time >= self._next_trigger_time:
|
|
129
|
+
self._export()
|
|
130
|
+
self._reset_trigger_time()
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
time.sleep(WORKER_SLEEP_SECONDS)
|
|
134
|
+
|
|
135
|
+
# flush all on shutdown
|
|
136
|
+
self._export()
|
|
137
|
+
|
|
138
|
+
def _export(self) -> None:
|
|
139
|
+
if self.client:
|
|
140
|
+
self._exporter.export(self.client, self._queue)
|
|
141
|
+
|
|
142
|
+
def _reset_trigger_time(self) -> None:
|
|
143
|
+
self._next_trigger_time = time.time() + self._trigger_cadence
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
_global_tracing_queue_manager: Optional[TraceQueueManager] = None
|
|
147
|
+
_queue_manager_lock = threading.Lock()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def init(client: Optional[SGPClient] = None, disabled: Optional[bool] = None) -> None:
|
|
151
|
+
"""Initialize the tracing backend
|
|
152
|
+
|
|
153
|
+
Good practice to always include this method call with a valid client at your program entrypoint.
|
|
154
|
+
|
|
155
|
+
Tracing will attempt to generate a default client if unprovided.
|
|
156
|
+
|
|
157
|
+
:param client: SGPClient
|
|
158
|
+
:param disabled: Set to True to disable tracing. Overrides environment variable ``DISABLE_SCALE_TRACING``
|
|
159
|
+
"""
|
|
160
|
+
if disabled is not None:
|
|
161
|
+
configure(disabled=disabled)
|
|
162
|
+
|
|
163
|
+
global _global_tracing_queue_manager
|
|
164
|
+
if _global_tracing_queue_manager is not None:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
with _queue_manager_lock:
|
|
168
|
+
if _global_tracing_queue_manager is None:
|
|
169
|
+
_global_tracing_queue_manager = TraceQueueManager(client)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def tracing_queue_manager() -> TraceQueueManager:
|
|
173
|
+
global _global_tracing_queue_manager
|
|
174
|
+
if _global_tracing_queue_manager is None:
|
|
175
|
+
init(None)
|
|
176
|
+
|
|
177
|
+
if _global_tracing_queue_manager is None:
|
|
178
|
+
# should never happen... useful for linting
|
|
179
|
+
raise RuntimeError("Tracing queue manager failed to initialize.")
|
|
180
|
+
|
|
181
|
+
return _global_tracing_queue_manager
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Union, Optional
|
|
3
|
+
|
|
4
|
+
from .span import Span, BaseSpan, NoOpSpan
|
|
5
|
+
from .util import is_disabled
|
|
6
|
+
from .scope import Scope
|
|
7
|
+
from .trace import Trace, BaseTrace, NoOpTrace
|
|
8
|
+
from .trace_queue_manager import tracing_queue_manager
|
|
9
|
+
|
|
10
|
+
log: logging.Logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def current_span() -> Optional[BaseSpan]:
|
|
14
|
+
"""Retrieves the currently active span from the execution context.
|
|
15
|
+
|
|
16
|
+
This function relies on `contextvars` to manage the active span in
|
|
17
|
+
a context-local manner, making it safe for concurrent execution
|
|
18
|
+
environments (e.g., threads, asyncio tasks).
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Optional[BaseSpan]: The current BaseSpan instance if one is active,
|
|
22
|
+
otherwise None. This could be a 'Span' or 'NoOpSpan'.
|
|
23
|
+
"""
|
|
24
|
+
return Scope.get_current_span()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def current_trace() -> Optional[BaseTrace]:
|
|
28
|
+
"""Retrieves the currently active trace from the execution context.
|
|
29
|
+
|
|
30
|
+
Similarly, to `current_span()`, this uses `contextvars` for context-local
|
|
31
|
+
trace management.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Optional[BaseTrace]: The current BaseTrace instance if one is active,
|
|
35
|
+
otherwise None. This could be a 'Trace' or 'NoOpTrace'.
|
|
36
|
+
"""
|
|
37
|
+
return Scope.get_current_trace()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def flush_queue() -> None:
|
|
41
|
+
"""
|
|
42
|
+
Blocking flush of all requests in the queue.
|
|
43
|
+
|
|
44
|
+
Useful for distributed applications to ensure spans have been committed before continuing.
|
|
45
|
+
:return:
|
|
46
|
+
"""
|
|
47
|
+
queue_manager = tracing_queue_manager()
|
|
48
|
+
queue_manager.flush_queue()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def create_trace(
|
|
52
|
+
name: str,
|
|
53
|
+
trace_id: Optional[str] = None,
|
|
54
|
+
root_span_id: Optional[str] = None,
|
|
55
|
+
metadata: Optional[Dict[str, Optional[str]]] = None,
|
|
56
|
+
) -> BaseTrace:
|
|
57
|
+
"""Creates a new trace and root span instance.
|
|
58
|
+
|
|
59
|
+
A trace represents a single, logical operation or workflow. It groups multiple
|
|
60
|
+
spans together. If tracing is disabled (via the 'DISABLE_SCALE_TRACING'
|
|
61
|
+
environment variable), a `NoOpTrace` instance is returned which performs no
|
|
62
|
+
actual tracing operations.
|
|
63
|
+
|
|
64
|
+
When a trace is started (e.g., by using it as a context manager or calling its
|
|
65
|
+
`start()` method), it becomes the `current_trace()` in the active scope.
|
|
66
|
+
Similarly, the root span instance becomes the `current_span()` in the active
|
|
67
|
+
scope.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
name: The name of the trace.
|
|
71
|
+
trace_id (Optional[str]): An optional, user-defined ID for the trace.
|
|
72
|
+
If None, a unique trace ID will be generated.
|
|
73
|
+
root_span_id (Optional[str]): An optional, user-defined ID for the root span.
|
|
74
|
+
metadata (Optional[Dict[str, Optional[str]]]): An optional, user-defined metadata.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
BaseTrace: A `Trace` instance if tracing is enabled, or a `NoOpTrace`
|
|
78
|
+
instance if tracing is disabled.
|
|
79
|
+
"""
|
|
80
|
+
if is_disabled():
|
|
81
|
+
log.debug(f"Tracing is disabled. Not creating a new trace.")
|
|
82
|
+
return NoOpTrace(name=name, trace_id=trace_id, root_span_id=root_span_id, metadata=metadata)
|
|
83
|
+
|
|
84
|
+
active_trace = current_trace()
|
|
85
|
+
if active_trace is not None:
|
|
86
|
+
log.warning(f"Trace with id {active_trace.trace_id} is already active. Creating a new trace anyways.")
|
|
87
|
+
|
|
88
|
+
trace = Trace(name=name, trace_id=trace_id, queue_manager=tracing_queue_manager(), metadata=metadata)
|
|
89
|
+
log.debug(f"Created new trace: {trace.trace_id}")
|
|
90
|
+
|
|
91
|
+
return trace
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def create_span(
|
|
95
|
+
name: str,
|
|
96
|
+
span_id: Optional[str] = None,
|
|
97
|
+
input: Optional[Dict[str, Any]] = None,
|
|
98
|
+
output: Optional[Dict[str, Any]] = None,
|
|
99
|
+
metadata: Optional[Dict[str, Optional[str]]] = None,
|
|
100
|
+
trace: Optional[Union[BaseTrace, str]] = None,
|
|
101
|
+
parent_span: Optional[BaseSpan] = None,
|
|
102
|
+
) -> BaseSpan:
|
|
103
|
+
"""Creates a new span instance.
|
|
104
|
+
|
|
105
|
+
A span represents a single unit of work or operation within a trace. Spans
|
|
106
|
+
can be nested to form a hierarchy.
|
|
107
|
+
|
|
108
|
+
If tracing is disabled (via 'DISABLE_SCALE_TRACING' environment variable),
|
|
109
|
+
a `NoOpSpan` is returned. Additionally, if no explicit `parent` (Trace or Span)
|
|
110
|
+
is provided and there is no `current_trace()` active in the scope, a `NoOpSpan`
|
|
111
|
+
will also be returned to prevent orphaned spans.
|
|
112
|
+
|
|
113
|
+
When a span is started (e.g., via context manager or `start()`), it becomes
|
|
114
|
+
the `current_span()` in the active scope.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name (str): A descriptive name for the span (e.g., "database_query",
|
|
118
|
+
"http_request").
|
|
119
|
+
span_id (Optional[str]): An optional, user-defined ID for the span.
|
|
120
|
+
input (Optional[dict[str, Any]], optional): A dictionary containing
|
|
121
|
+
input data or parameters relevant to this span's operation. Defaults to None.
|
|
122
|
+
output (Optional[dict[str, Any]], optional): A dictionary containing
|
|
123
|
+
output data or results from this span's operation. Defaults to None.
|
|
124
|
+
metadata (Optional[dict[str, Union[str, int, float, bool, None]]], optional):
|
|
125
|
+
A dictionary for arbitrary key-value pairs providing additional
|
|
126
|
+
context or annotations for the span. Values should be simple types.
|
|
127
|
+
Defaults to None.
|
|
128
|
+
trace (Optional[Union[BaseTrace, str]], optional): A `Trace` instance
|
|
129
|
+
or a trace ID. Used for explicit control. Default to trace fetched
|
|
130
|
+
from the active scope.
|
|
131
|
+
parent_span (Optional[BaseSpan], optional): A `Span` instance. Like
|
|
132
|
+
trace, used for explicit control. Defaults to span fetched from the
|
|
133
|
+
active scope.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
BaseSpan: A `Span` instance if tracing is enabled and a valid trace context
|
|
137
|
+
exists, or a `NoOpSpan` otherwise.
|
|
138
|
+
"""
|
|
139
|
+
queue_manager = tracing_queue_manager()
|
|
140
|
+
parent_span_id: Optional[str] = None
|
|
141
|
+
|
|
142
|
+
if parent_span is not None:
|
|
143
|
+
trace_id = parent_span.trace_id
|
|
144
|
+
parent_span_id = parent_span.span_id
|
|
145
|
+
elif trace is not None:
|
|
146
|
+
trace_id = trace if isinstance(trace, str) else trace.trace_id
|
|
147
|
+
|
|
148
|
+
parent_span = Scope.get_current_span()
|
|
149
|
+
parent_span_id = parent_span.span_id if parent_span else None
|
|
150
|
+
else:
|
|
151
|
+
trace = Scope.get_current_trace()
|
|
152
|
+
parent_span = Scope.get_current_span()
|
|
153
|
+
|
|
154
|
+
parent_span_id = parent_span.span_id if parent_span else None
|
|
155
|
+
|
|
156
|
+
if trace is None:
|
|
157
|
+
# need to think about default behavior here... do we create a trace, some other options?
|
|
158
|
+
# I am leaning towards setting it as an option as sometimes people might want to be succinct or when we
|
|
159
|
+
# build decorators we might want this functionality
|
|
160
|
+
log.debug(f"attempting to create a span with no trace")
|
|
161
|
+
return NoOpSpan(name=name, span_id=span_id, parent_span_id=parent_span_id)
|
|
162
|
+
|
|
163
|
+
trace_id = trace.trace_id
|
|
164
|
+
|
|
165
|
+
if is_disabled():
|
|
166
|
+
return NoOpSpan(name=name, span_id=span_id, parent_span_id=parent_span_id, trace_id=trace_id)
|
|
167
|
+
|
|
168
|
+
span = Span(
|
|
169
|
+
name=name,
|
|
170
|
+
span_id=span_id,
|
|
171
|
+
parent_span_id=parent_span_id,
|
|
172
|
+
trace_id=trace_id,
|
|
173
|
+
input=input or {},
|
|
174
|
+
output=output or {},
|
|
175
|
+
metadata=metadata or {},
|
|
176
|
+
queue_manager=queue_manager,
|
|
177
|
+
)
|
|
178
|
+
log.debug(f"Created new span: {span.span_id}")
|
|
179
|
+
|
|
180
|
+
return span
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is necessary, unfortunately. Stainless does not provide these as enums, only as type annotations.
|
|
3
|
+
|
|
4
|
+
For strict linting, we need to reference these enums.
|
|
5
|
+
|
|
6
|
+
NOTE: These will have to be manually updated to support updated span_types and status.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Literal
|
|
10
|
+
|
|
11
|
+
SpanStatusLiterals = Literal["SUCCESS", "ERROR"]
|
|
12
|
+
|
|
13
|
+
SpanTypeLiterals = Literal[
|
|
14
|
+
"TEXT_INPUT",
|
|
15
|
+
"TEXT_OUTPUT",
|
|
16
|
+
"COMPLETION_INPUT",
|
|
17
|
+
"COMPLETION",
|
|
18
|
+
"KB_RETRIEVAL",
|
|
19
|
+
"KB_INPUT",
|
|
20
|
+
"RERANKING",
|
|
21
|
+
"EXTERNAL_ENDPOINT",
|
|
22
|
+
"PROMPT_ENGINEERING",
|
|
23
|
+
"DOCUMENT_INPUT",
|
|
24
|
+
"MAP_REDUCE",
|
|
25
|
+
"DOCUMENT_SEARCH",
|
|
26
|
+
"DOCUMENT_PROMPT",
|
|
27
|
+
"CUSTOM",
|
|
28
|
+
"INPUT_GUARDRAIL",
|
|
29
|
+
"OUTPUT_GUARDRAIL",
|
|
30
|
+
"CODE_EXECUTION",
|
|
31
|
+
"DATA_MANIPULATION",
|
|
32
|
+
"EVALUATION",
|
|
33
|
+
"FILE_RETRIEVAL",
|
|
34
|
+
"KB_ADD_CHUNK",
|
|
35
|
+
"KB_MANAGEMENT",
|
|
36
|
+
"TRACER",
|
|
37
|
+
"AGENT_TRACER",
|
|
38
|
+
"AGENT_WORKFLOW",
|
|
39
|
+
"STANDALONE",
|
|
40
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from functools import cache
|
|
6
|
+
|
|
7
|
+
_tracing_disabled: Optional[bool] = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_trace_id() -> str:
|
|
11
|
+
"""
|
|
12
|
+
Generate a unique trace ID.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: A unique trace ID.
|
|
16
|
+
"""
|
|
17
|
+
return str(uuid.uuid4())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate_span_id() -> str:
|
|
21
|
+
"""
|
|
22
|
+
Generate a unique span ID.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
str: A unique span ID.
|
|
26
|
+
"""
|
|
27
|
+
return str(uuid.uuid4())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def iso_timestamp() -> str:
|
|
31
|
+
"""
|
|
32
|
+
Generate an ISO 8601 timestamp.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
str: The current time in ISO 8601 format.
|
|
36
|
+
"""
|
|
37
|
+
return datetime.now(timezone.utc).isoformat()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def configure(disabled: bool) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Programmatically enable or disable tracing.
|
|
43
|
+
|
|
44
|
+
This setting takes precedence over the `DISABLE_SCALE_TRACING` environment
|
|
45
|
+
variable.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
disabled (bool): Set to True to disable tracing, False to enable.
|
|
49
|
+
"""
|
|
50
|
+
global _tracing_disabled
|
|
51
|
+
_tracing_disabled = disabled
|
|
52
|
+
is_disabled.cache_clear()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@cache
|
|
56
|
+
def is_disabled() -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Check if tracing is disabled, with programmatic control taking precedence.
|
|
59
|
+
|
|
60
|
+
Tracing is considered disabled if `configure(disabled=True)` has been called,
|
|
61
|
+
or if the `DISABLE_SCALE_TRACING` environment variable is set.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
bool: True if tracing is disabled, otherwise False.
|
|
65
|
+
"""
|
|
66
|
+
if _tracing_disabled is not None:
|
|
67
|
+
return _tracing_disabled
|
|
68
|
+
return os.getenv("DISABLE_SCALE_TRACING") is not None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scale-gp-beta
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a19
|
|
4
4
|
Summary: The official Python library for the Scale GP API
|
|
5
5
|
Project-URL: Homepage, https://github.com/scaleapi/sgp-python-beta
|
|
6
6
|
Project-URL: Repository, https://github.com/scaleapi/sgp-python-beta
|
|
@@ -11,7 +11,7 @@ scale_gp_beta/_resource.py,sha256=siZly_U6D0AOVLAzaOsqUdEFFzVMbWRj-ml30nvRp7E,11
|
|
|
11
11
|
scale_gp_beta/_response.py,sha256=GemuybPk0uemovTlGHyHkj-ScYTTDJA0jqH5FQqIPwQ,28852
|
|
12
12
|
scale_gp_beta/_streaming.py,sha256=fcCSGXslmi2SmmkM05g2SACXHk2Mj7k1X5uMBu6U5s8,10112
|
|
13
13
|
scale_gp_beta/_types.py,sha256=0wSs40TefKMPBj2wQKenEeZ0lzedoHClNJeqrpAgkII,6204
|
|
14
|
-
scale_gp_beta/_version.py,sha256=
|
|
14
|
+
scale_gp_beta/_version.py,sha256=QpUK6D2Mrqt-7NO3RPbtzHQemQKxo2IcY3na8iBT4mE,174
|
|
15
15
|
scale_gp_beta/pagination.py,sha256=t-U86PYxl20VRsz8VXOMJJDe7HxkX7ISFMvRNbBNy9s,4054
|
|
16
16
|
scale_gp_beta/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
scale_gp_beta/_utils/__init__.py,sha256=PNZ_QJuzZEgyYXqkO1HVhGkj5IU9bglVUcw7H-Knjzw,2062
|
|
@@ -25,6 +25,16 @@ scale_gp_beta/_utils/_transform.py,sha256=n7kskEWz6o__aoNvhFoGVyDoalNe6mJwp-g7BW
|
|
|
25
25
|
scale_gp_beta/_utils/_typing.py,sha256=D0DbbNu8GnYQTSICnTSHDGsYXj8TcAKyhejb0XcnjtY,4602
|
|
26
26
|
scale_gp_beta/_utils/_utils.py,sha256=ts4CiiuNpFiGB6YMdkQRh2SZvYvsl7mAF-JWHCcLDf4,12312
|
|
27
27
|
scale_gp_beta/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
|
|
28
|
+
scale_gp_beta/lib/tracing/__init__.py,sha256=UgyExbqAA2ljDEF4X4YFhtbBZuoQJ2IF4hkGs_xQEc0,226
|
|
29
|
+
scale_gp_beta/lib/tracing/exceptions.py,sha256=vL2_GAfWEy8EfLhrBkDClLYTasOLnL-5zUpdCQnSzcs,107
|
|
30
|
+
scale_gp_beta/lib/tracing/scope.py,sha256=kHrd0his8L2K_KXn2E6J9d565PliEdFoKRQ1d5ALTyk,3901
|
|
31
|
+
scale_gp_beta/lib/tracing/span.py,sha256=qWeqiIuEDtCAPApMq5q_UWe9of204EStguja7j5eaXc,8143
|
|
32
|
+
scale_gp_beta/lib/tracing/trace.py,sha256=jDZeT5-cE70FcsjC18CEGBdbDqYX36LqlnLIavK98RI,3382
|
|
33
|
+
scale_gp_beta/lib/tracing/trace_exporter.py,sha256=dr8DfbHhUaJLcywuzfmoD_WQ30mXuKzDUHw5QWNWLfg,3032
|
|
34
|
+
scale_gp_beta/lib/tracing/trace_queue_manager.py,sha256=DCR239JuOTr76Lo5jBPDhi8ZCqxCdH9GklXKuOyzUqE,5880
|
|
35
|
+
scale_gp_beta/lib/tracing/tracing.py,sha256=xht7oAzxbgIE3fgH_botEDxBDKBr_8r5jrtFwtaVmiQ,7193
|
|
36
|
+
scale_gp_beta/lib/tracing/types.py,sha256=CKdOHYIEZGQXWFSrn10iC-cyRw6_Lido9jC8pdSc3P0,1010
|
|
37
|
+
scale_gp_beta/lib/tracing/util.py,sha256=8Oq4wLXRNOzh3CC1zRaBEr0h_WdXLrk536BUNKRddVE,1527
|
|
28
38
|
scale_gp_beta/resources/__init__.py,sha256=Fyo05_2_pc5orfyTSIpxa3btmBTd45VasgibwSqbbKo,4942
|
|
29
39
|
scale_gp_beta/resources/completions.py,sha256=4esj9lGTJAxt6wFvON126DvEGkMIChRZ6uZBOf56Aac,31868
|
|
30
40
|
scale_gp_beta/resources/dataset_items.py,sha256=2d7O5zmqVEafJTxVwgbRz9yq-4T81dPPfFuPDRAaWqU,22510
|
|
@@ -107,7 +117,7 @@ scale_gp_beta/types/chat/completion_models_params.py,sha256=ETxafJIUx4tTvkiR-ZCr
|
|
|
107
117
|
scale_gp_beta/types/chat/completion_models_response.py,sha256=Ctgj6o-QWPSdjBKzG9J4Id0-DjXu4UGGw1NR6-840Ec,403
|
|
108
118
|
scale_gp_beta/types/chat/model_definition.py,sha256=NNgopTm900GD0Zs2YHkcvoW67uKaWUKVyPbhKBHvKdQ,817
|
|
109
119
|
scale_gp_beta/types/files/__init__.py,sha256=OKfJYcKb4NObdiRObqJV_dOyDQ8feXekDUge2o_4pXQ,122
|
|
110
|
-
scale_gp_beta-0.1.
|
|
111
|
-
scale_gp_beta-0.1.
|
|
112
|
-
scale_gp_beta-0.1.
|
|
113
|
-
scale_gp_beta-0.1.
|
|
120
|
+
scale_gp_beta-0.1.0a19.dist-info/METADATA,sha256=pOAEZo76uYkcEFTkgSza0Di3tD0gvC9vZCX3qWn4ySA,16881
|
|
121
|
+
scale_gp_beta-0.1.0a19.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
122
|
+
scale_gp_beta-0.1.0a19.dist-info/licenses/LICENSE,sha256=x49Bj8r_ZpqfzThbmfHyZ_bE88XvHdIMI_ANyLHFFRE,11338
|
|
123
|
+
scale_gp_beta-0.1.0a19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|