aiqa-client 0.3.5__py3-none-any.whl → 0.3.7__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.
- aiqa/__init__.py +1 -7
- aiqa/aiqa_exporter.py +39 -17
- aiqa/client.py +121 -29
- aiqa/constants.py +5 -0
- aiqa/tracing.py +24 -35
- {aiqa_client-0.3.5.dist-info → aiqa_client-0.3.7.dist-info}/METADATA +30 -3
- aiqa_client-0.3.7.dist-info/RECORD +15 -0
- aiqa_client-0.3.5.dist-info/RECORD +0 -14
- {aiqa_client-0.3.5.dist-info → aiqa_client-0.3.7.dist-info}/WHEEL +0 -0
- {aiqa_client-0.3.5.dist-info → aiqa_client-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {aiqa_client-0.3.5.dist-info → aiqa_client-0.3.7.dist-info}/top_level.txt +0 -0
aiqa/__init__.py
CHANGED
|
@@ -22,12 +22,9 @@ Example:
|
|
|
22
22
|
from .tracing import (
|
|
23
23
|
WithTracing,
|
|
24
24
|
flush_tracing,
|
|
25
|
-
shutdown_tracing,
|
|
26
25
|
set_span_attribute,
|
|
27
26
|
set_span_name,
|
|
28
27
|
get_active_span,
|
|
29
|
-
get_provider,
|
|
30
|
-
get_exporter,
|
|
31
28
|
get_active_trace_id,
|
|
32
29
|
get_span_id,
|
|
33
30
|
create_span_from_trace_id,
|
|
@@ -40,17 +37,14 @@ from .tracing import (
|
|
|
40
37
|
from .client import get_aiqa_client
|
|
41
38
|
from .experiment_runner import ExperimentRunner
|
|
42
39
|
|
|
43
|
-
__version__ = "0.3.
|
|
40
|
+
__version__ = "0.3.7"
|
|
44
41
|
|
|
45
42
|
__all__ = [
|
|
46
43
|
"WithTracing",
|
|
47
44
|
"flush_tracing",
|
|
48
|
-
"shutdown_tracing",
|
|
49
45
|
"set_span_attribute",
|
|
50
46
|
"set_span_name",
|
|
51
47
|
"get_active_span",
|
|
52
|
-
"get_provider",
|
|
53
|
-
"get_exporter",
|
|
54
48
|
"get_aiqa_client",
|
|
55
49
|
"ExperimentRunner",
|
|
56
50
|
"get_active_trace_id",
|
aiqa/aiqa_exporter.py
CHANGED
|
@@ -13,6 +13,9 @@ from typing import List, Dict, Any, Optional
|
|
|
13
13
|
from opentelemetry.sdk.trace import ReadableSpan
|
|
14
14
|
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
15
15
|
|
|
16
|
+
from .constants import AIQA_TRACER_NAME
|
|
17
|
+
from . import __version__
|
|
18
|
+
|
|
16
19
|
logger = logging.getLogger("AIQA")
|
|
17
20
|
|
|
18
21
|
|
|
@@ -28,6 +31,7 @@ class AIQASpanExporter(SpanExporter):
|
|
|
28
31
|
api_key: Optional[str] = None,
|
|
29
32
|
flush_interval_seconds: float = 5.0,
|
|
30
33
|
max_batch_size_bytes: int = 5 * 1024 * 1024, # 5MB default
|
|
34
|
+
max_buffer_spans: int = 10000, # Maximum spans to buffer (prevents unbounded growth)
|
|
31
35
|
):
|
|
32
36
|
"""
|
|
33
37
|
Initialize the AIQA span exporter.
|
|
@@ -42,6 +46,7 @@ class AIQASpanExporter(SpanExporter):
|
|
|
42
46
|
self._api_key = api_key
|
|
43
47
|
self.flush_interval_ms = flush_interval_seconds * 1000
|
|
44
48
|
self.max_batch_size_bytes = max_batch_size_bytes
|
|
49
|
+
self.max_buffer_spans = max_buffer_spans
|
|
45
50
|
self.buffer: List[Dict[str, Any]] = []
|
|
46
51
|
self.buffer_span_keys: set = set() # Track (traceId, spanId) tuples to prevent duplicates (Python 3.8 compatible)
|
|
47
52
|
self.buffer_lock = threading.Lock()
|
|
@@ -71,12 +76,30 @@ class AIQASpanExporter(SpanExporter):
|
|
|
71
76
|
if not spans:
|
|
72
77
|
logger.debug("export() called with empty spans list")
|
|
73
78
|
return SpanExportResult.SUCCESS
|
|
79
|
+
|
|
80
|
+
# Check if AIQA tracing is enabled
|
|
81
|
+
try:
|
|
82
|
+
from .client import get_aiqa_client
|
|
83
|
+
client = get_aiqa_client()
|
|
84
|
+
if not client.enabled:
|
|
85
|
+
logger.debug(f"AIQA export() skipped: tracing is disabled")
|
|
86
|
+
return SpanExportResult.SUCCESS
|
|
87
|
+
except Exception:
|
|
88
|
+
# If we can't check enabled status, proceed (fail open)
|
|
89
|
+
pass
|
|
90
|
+
|
|
74
91
|
logger.debug(f"AIQA export() called with {len(spans)} spans")
|
|
75
92
|
# Serialize and add to buffer, deduplicating by (traceId, spanId)
|
|
76
93
|
with self.buffer_lock:
|
|
77
94
|
serialized_spans = []
|
|
78
95
|
duplicates_count = 0
|
|
96
|
+
dropped_count = 0
|
|
79
97
|
for span in spans:
|
|
98
|
+
# Check if buffer is full (prevent unbounded growth)
|
|
99
|
+
if len(self.buffer) >= self.max_buffer_spans:
|
|
100
|
+
dropped_count += 1
|
|
101
|
+
continue
|
|
102
|
+
|
|
80
103
|
serialized = self._serialize_span(span)
|
|
81
104
|
span_key = (serialized["traceId"], serialized["spanId"])
|
|
82
105
|
if span_key not in self.buffer_span_keys:
|
|
@@ -88,6 +111,12 @@ class AIQASpanExporter(SpanExporter):
|
|
|
88
111
|
|
|
89
112
|
self.buffer.extend(serialized_spans)
|
|
90
113
|
buffer_size = len(self.buffer)
|
|
114
|
+
|
|
115
|
+
if dropped_count > 0:
|
|
116
|
+
logger.warning(
|
|
117
|
+
f"WARNING: Buffer full ({buffer_size} spans), dropped {dropped_count} span(s). "
|
|
118
|
+
f"Consider increasing max_buffer_spans or fixing server connectivity."
|
|
119
|
+
)
|
|
91
120
|
|
|
92
121
|
if duplicates_count > 0:
|
|
93
122
|
logger.debug(
|
|
@@ -160,10 +189,16 @@ class AIQASpanExporter(SpanExporter):
|
|
|
160
189
|
"traceFlags": span_context.trace_flags,
|
|
161
190
|
"duration": self._time_to_tuple(span.end_time - span.start_time) if span.end_time else None,
|
|
162
191
|
"ended": span.end_time is not None,
|
|
163
|
-
"instrumentationLibrary":
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
192
|
+
"instrumentationLibrary": self._get_instrumentation_library(span),
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
def _get_instrumentation_library(self, span: ReadableSpan) -> Dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Get instrumentation library information from the span: just use the package version.
|
|
198
|
+
"""
|
|
199
|
+
return {
|
|
200
|
+
"name": AIQA_TRACER_NAME,
|
|
201
|
+
"version": __version__,
|
|
167
202
|
}
|
|
168
203
|
|
|
169
204
|
def _time_to_tuple(self, nanoseconds: int) -> tuple:
|
|
@@ -171,19 +206,6 @@ class AIQASpanExporter(SpanExporter):
|
|
|
171
206
|
seconds = int(nanoseconds // 1_000_000_000)
|
|
172
207
|
nanos = int(nanoseconds % 1_000_000_000)
|
|
173
208
|
return (seconds, nanos)
|
|
174
|
-
|
|
175
|
-
def _get_instrumentation_name(self) -> str:
|
|
176
|
-
"""Get instrumentation library name - always 'aiqa-tracer'."""
|
|
177
|
-
from .client import AIQA_TRACER_NAME
|
|
178
|
-
return AIQA_TRACER_NAME
|
|
179
|
-
|
|
180
|
-
def _get_instrumentation_version(self) -> Optional[str]:
|
|
181
|
-
"""Get instrumentation library version from __version__."""
|
|
182
|
-
try:
|
|
183
|
-
from . import __version__
|
|
184
|
-
return __version__
|
|
185
|
-
except (ImportError, AttributeError):
|
|
186
|
-
return None
|
|
187
209
|
|
|
188
210
|
def _build_request_headers(self) -> Dict[str, str]:
|
|
189
211
|
"""Build HTTP headers for span requests."""
|
aiqa/client.py
CHANGED
|
@@ -2,16 +2,20 @@
|
|
|
2
2
|
import os
|
|
3
3
|
import logging
|
|
4
4
|
from functools import lru_cache
|
|
5
|
+
from typing import Optional, TYPE_CHECKING, Any
|
|
5
6
|
from opentelemetry import trace
|
|
6
7
|
from opentelemetry.sdk.trace import TracerProvider
|
|
7
8
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
8
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .aiqa_exporter import AIQASpanExporter
|
|
12
|
+
|
|
9
13
|
logger = logging.getLogger("AIQA")
|
|
10
14
|
|
|
11
15
|
# Compatibility import for TraceIdRatioBased sampler
|
|
12
16
|
# In older OpenTelemetry versions it was TraceIdRatioBasedSampler
|
|
13
17
|
# In newer versions (>=1.24.0) it's TraceIdRatioBased
|
|
14
|
-
TraceIdRatioBased = None
|
|
18
|
+
TraceIdRatioBased: Optional[Any] = None
|
|
15
19
|
try:
|
|
16
20
|
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
|
|
17
21
|
except ImportError:
|
|
@@ -27,14 +31,86 @@ except ImportError:
|
|
|
27
31
|
# Set to None so we can check later
|
|
28
32
|
TraceIdRatioBased = None
|
|
29
33
|
|
|
30
|
-
from .
|
|
34
|
+
from .constants import AIQA_TRACER_NAME
|
|
35
|
+
|
|
36
|
+
class AIQAClient:
|
|
37
|
+
"""
|
|
38
|
+
Singleton client for AIQA tracing.
|
|
39
|
+
|
|
40
|
+
This class manages the tracing provider, exporter, and enabled state.
|
|
41
|
+
Access via get_aiqa_client() which returns the singleton instance.
|
|
42
|
+
"""
|
|
43
|
+
_instance: Optional['AIQAClient'] = None
|
|
44
|
+
|
|
45
|
+
def __new__(cls):
|
|
46
|
+
if cls._instance is None:
|
|
47
|
+
cls._instance = super().__new__(cls)
|
|
48
|
+
cls._instance._provider: Optional[TracerProvider] = None
|
|
49
|
+
cls._instance._exporter: Optional[AIQASpanExporter] = None
|
|
50
|
+
cls._instance._enabled: bool = True
|
|
51
|
+
cls._instance._initialized: bool = False
|
|
52
|
+
return cls._instance
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def provider(self) -> Optional[TracerProvider]:
|
|
56
|
+
"""Get the tracer provider."""
|
|
57
|
+
return self._provider
|
|
58
|
+
|
|
59
|
+
@provider.setter
|
|
60
|
+
def provider(self, value: Optional[TracerProvider]) -> None:
|
|
61
|
+
"""Set the tracer provider."""
|
|
62
|
+
self._provider = value
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def exporter(self) -> Optional[AIQASpanExporter]:
|
|
66
|
+
"""Get the span exporter."""
|
|
67
|
+
return self._exporter
|
|
68
|
+
|
|
69
|
+
@exporter.setter
|
|
70
|
+
def exporter(self, value: Optional[AIQASpanExporter]) -> None:
|
|
71
|
+
"""Set the span exporter."""
|
|
72
|
+
self._exporter = value
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def enabled(self) -> bool:
|
|
76
|
+
"""Check if tracing is enabled."""
|
|
77
|
+
return self._enabled
|
|
78
|
+
|
|
79
|
+
@enabled.setter
|
|
80
|
+
def enabled(self, value: bool) -> None:
|
|
81
|
+
"""Set the enabled state.
|
|
82
|
+
|
|
83
|
+
When disabled:
|
|
84
|
+
- Tracing does not create spans
|
|
85
|
+
- Export does not send spans
|
|
86
|
+
"""
|
|
87
|
+
logger.info(f"AIQA tracing {'enabled' if value else 'disabled'}")
|
|
88
|
+
self._enabled = value
|
|
89
|
+
|
|
90
|
+
def shutdown(self) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Shutdown the tracer provider and exporter.
|
|
93
|
+
It is not necessary to call this function.
|
|
94
|
+
Use this to clean up resources at the end of all tracing.
|
|
95
|
+
|
|
96
|
+
This will also set enabled=False to prevent further tracing attempts.
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
logger.info("AIQA tracing shutting down")
|
|
100
|
+
# Disable tracing to prevent attempts to use shut-down system
|
|
101
|
+
self.enabled = False
|
|
102
|
+
if self._provider:
|
|
103
|
+
self._provider.shutdown()
|
|
104
|
+
if self._exporter:
|
|
105
|
+
self._exporter.shutdown()
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"Error shutting down tracing: {e}")
|
|
108
|
+
# Still disable even if shutdown had errors
|
|
109
|
+
self.enabled = False
|
|
31
110
|
|
|
32
|
-
AIQA_TRACER_NAME = "aiqa-tracer"
|
|
33
111
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"exporter": None,
|
|
37
|
-
}
|
|
112
|
+
# Global singleton instance
|
|
113
|
+
client: AIQAClient = AIQAClient()
|
|
38
114
|
|
|
39
115
|
# Component tag to add to all spans (can be set via AIQA_COMPONENT_TAG env var or programmatically)
|
|
40
116
|
_component_tag: str = ""
|
|
@@ -52,14 +128,17 @@ def set_component_tag(tag: str | None) -> None:
|
|
|
52
128
|
|
|
53
129
|
|
|
54
130
|
@lru_cache(maxsize=1)
|
|
55
|
-
def get_aiqa_client():
|
|
131
|
+
def get_aiqa_client() -> AIQAClient:
|
|
56
132
|
"""
|
|
57
|
-
Initialize and return the AIQA client.
|
|
133
|
+
Initialize and return the AIQA client singleton.
|
|
58
134
|
|
|
59
135
|
This function must be called before using any AIQA tracing functionality to ensure
|
|
60
136
|
that environment variables (such as AIQA_SERVER_URL, AIQA_API_KEY, AIQA_COMPONENT_TAG)
|
|
61
137
|
are properly loaded and the tracing system is initialized.
|
|
62
138
|
|
|
139
|
+
The client object manages the tracing system state. Tracing is done by the WithTracing
|
|
140
|
+
decorator. Experiments are run by the ExperimentRunner class.
|
|
141
|
+
|
|
63
142
|
The function is idempotent - calling it multiple times is safe and will only
|
|
64
143
|
initialize once.
|
|
65
144
|
|
|
@@ -67,7 +146,7 @@ def get_aiqa_client():
|
|
|
67
146
|
from aiqa import get_aiqa_client, WithTracing
|
|
68
147
|
|
|
69
148
|
# Initialize client (loads env vars)
|
|
70
|
-
get_aiqa_client()
|
|
149
|
+
client = get_aiqa_client()
|
|
71
150
|
|
|
72
151
|
@WithTracing
|
|
73
152
|
def my_function():
|
|
@@ -79,12 +158,27 @@ def get_aiqa_client():
|
|
|
79
158
|
except Exception as e:
|
|
80
159
|
logger.error(f"Failed to initialize AIQA tracing: {e}")
|
|
81
160
|
logger.warning("AIQA tracing is disabled. Your application will continue to run without tracing.")
|
|
82
|
-
# optionally return a richer client object; for now you just need init
|
|
83
161
|
return client
|
|
84
162
|
|
|
85
|
-
def _init_tracing():
|
|
163
|
+
def _init_tracing() -> None:
|
|
86
164
|
"""Initialize tracing system and load configuration from environment variables."""
|
|
165
|
+
global client
|
|
166
|
+
if client._initialized:
|
|
167
|
+
return
|
|
168
|
+
|
|
87
169
|
try:
|
|
170
|
+
server_url = os.getenv("AIQA_SERVER_URL")
|
|
171
|
+
api_key = os.getenv("AIQA_API_KEY")
|
|
172
|
+
|
|
173
|
+
if not server_url or not api_key:
|
|
174
|
+
client.enabled = False
|
|
175
|
+
missing_vars = [var for var, val in [("AIQA_SERVER_URL", server_url), ("AIQA_API_KEY", api_key)] if not val]
|
|
176
|
+
logger.warning(
|
|
177
|
+
f"AIQA tracing is disabled: missing required environment variables: {', '.join(missing_vars)}"
|
|
178
|
+
)
|
|
179
|
+
client._initialized = True
|
|
180
|
+
return
|
|
181
|
+
|
|
88
182
|
# Initialize component tag from environment variable
|
|
89
183
|
set_component_tag(os.getenv("AIQA_COMPONENT_TAG", None))
|
|
90
184
|
|
|
@@ -114,21 +208,23 @@ def _init_tracing():
|
|
|
114
208
|
|
|
115
209
|
# Idempotently add your processor
|
|
116
210
|
_attach_aiqa_processor(provider)
|
|
117
|
-
|
|
118
|
-
client["provider"] = provider
|
|
211
|
+
client.provider = provider
|
|
119
212
|
|
|
120
213
|
# Log successful initialization
|
|
121
|
-
server_url = os.getenv("AIQA_SERVER_URL", "not configured")
|
|
122
214
|
logger.info(f"AIQA initialized and tracing (sampling rate: {sampling_rate:.2f}, server: {server_url})")
|
|
215
|
+
client._initialized = True
|
|
123
216
|
|
|
124
217
|
except Exception as e:
|
|
125
218
|
logger.error(f"Error initializing AIQA tracing: {e}")
|
|
219
|
+
client._initialized = True # Mark as initialized even on error to prevent retry loops
|
|
126
220
|
raise
|
|
127
221
|
|
|
128
|
-
def _attach_aiqa_processor(provider: TracerProvider):
|
|
222
|
+
def _attach_aiqa_processor(provider: TracerProvider) -> None:
|
|
129
223
|
"""Attach AIQA span processor to the provider. Idempotent - safe to call multiple times."""
|
|
224
|
+
from .aiqa_exporter import AIQASpanExporter
|
|
225
|
+
|
|
130
226
|
try:
|
|
131
|
-
#
|
|
227
|
+
# Check if already attached
|
|
132
228
|
for p in provider._active_span_processor._span_processors:
|
|
133
229
|
if isinstance(getattr(p, "exporter", None), AIQASpanExporter):
|
|
134
230
|
logger.debug("AIQA span processor already attached, skipping")
|
|
@@ -140,7 +236,7 @@ def _attach_aiqa_processor(provider: TracerProvider):
|
|
|
140
236
|
)
|
|
141
237
|
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
142
238
|
global client
|
|
143
|
-
client
|
|
239
|
+
client.exporter = exporter
|
|
144
240
|
logger.debug("AIQA span processor attached successfully")
|
|
145
241
|
except Exception as e:
|
|
146
242
|
logger.error(f"Error attaching AIQA span processor: {e}")
|
|
@@ -148,23 +244,19 @@ def _attach_aiqa_processor(provider: TracerProvider):
|
|
|
148
244
|
raise
|
|
149
245
|
|
|
150
246
|
|
|
151
|
-
|
|
247
|
+
|
|
248
|
+
def get_aiqa_tracer() -> trace.Tracer:
|
|
152
249
|
"""
|
|
153
250
|
Get the AIQA tracer with version from __init__.py __version__.
|
|
154
|
-
This should be used instead of trace.get_tracer()
|
|
251
|
+
This should be used instead of trace.get_tracer() so that the version is set.
|
|
155
252
|
"""
|
|
156
253
|
try:
|
|
157
254
|
# Import here to avoid circular import
|
|
158
255
|
from . import __version__
|
|
159
|
-
|
|
160
256
|
# Compatibility: version parameter may not be supported in older OpenTelemetry versions
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return trace.get_tracer(AIQA_TRACER_NAME, version=__version__)
|
|
164
|
-
except TypeError:
|
|
165
|
-
# Fall back to without version parameter (older versions)
|
|
166
|
-
return trace.get_tracer(AIQA_TRACER_NAME)
|
|
257
|
+
# Try with version parameter (newer OpenTelemetry versions)
|
|
258
|
+
return trace.get_tracer(AIQA_TRACER_NAME, version=__version__)
|
|
167
259
|
except Exception as e:
|
|
168
|
-
|
|
169
|
-
|
|
260
|
+
# Log issue but still return a tracer
|
|
261
|
+
logger.info(f"Issue getting AIQA tracer with version: {e}, using fallback")
|
|
170
262
|
return trace.get_tracer(AIQA_TRACER_NAME)
|
aiqa/constants.py
ADDED
aiqa/tracing.py
CHANGED
|
@@ -14,7 +14,8 @@ from opentelemetry.sdk.trace import TracerProvider
|
|
|
14
14
|
from opentelemetry.trace import Status, StatusCode, SpanContext, TraceFlags
|
|
15
15
|
from opentelemetry.propagate import inject, extract
|
|
16
16
|
from .aiqa_exporter import AIQASpanExporter
|
|
17
|
-
from .client import get_aiqa_client,
|
|
17
|
+
from .client import get_aiqa_client, get_component_tag, set_component_tag as _set_component_tag, get_aiqa_tracer
|
|
18
|
+
from .constants import AIQA_TRACER_NAME
|
|
18
19
|
from .object_serialiser import serialize_for_span
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger("AIQA")
|
|
@@ -25,35 +26,21 @@ async def flush_tracing() -> None:
|
|
|
25
26
|
Flush all pending spans to the server.
|
|
26
27
|
Flushes also happen automatically every few seconds. So you only need to call this function
|
|
27
28
|
if you want to flush immediately, e.g. before exiting a process.
|
|
29
|
+
A common use is if you are tracing unit tests or experiment runs.
|
|
28
30
|
|
|
29
31
|
This flushes both the BatchSpanProcessor and the exporter buffer.
|
|
30
32
|
"""
|
|
31
33
|
client = get_aiqa_client()
|
|
32
|
-
if client.
|
|
33
|
-
client
|
|
34
|
-
if client.
|
|
35
|
-
await client
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
async def shutdown_tracing() -> None:
|
|
39
|
-
"""
|
|
40
|
-
Shutdown the tracer provider and exporter.
|
|
41
|
-
It is not necessary to call this function.
|
|
42
|
-
"""
|
|
43
|
-
try:
|
|
44
|
-
client = get_aiqa_client()
|
|
45
|
-
if client.get("provider"):
|
|
46
|
-
client["provider"].shutdown() # Synchronous method
|
|
47
|
-
if client.get("exporter"):
|
|
48
|
-
client["exporter"].shutdown() # Synchronous method
|
|
49
|
-
except Exception as e:
|
|
50
|
-
logger.error(f"Error shutting down tracing: {e}")
|
|
34
|
+
if client.provider:
|
|
35
|
+
client.provider.force_flush() # Synchronous method
|
|
36
|
+
if client.exporter:
|
|
37
|
+
await client.exporter.flush()
|
|
51
38
|
|
|
52
39
|
|
|
53
40
|
# Export provider and exporter accessors for advanced usage
|
|
54
41
|
|
|
55
42
|
__all__ = [
|
|
56
|
-
"
|
|
43
|
+
"flush_tracing", "WithTracing",
|
|
57
44
|
"set_span_attribute", "set_span_name", "get_active_span",
|
|
58
45
|
"get_active_trace_id", "get_span_id", "create_span_from_trace_id", "inject_trace_context", "extract_trace_context",
|
|
59
46
|
"set_conversation_id", "set_component_tag", "set_token_usage", "set_provider_and_model", "get_span", "submit_feedback"
|
|
@@ -640,7 +627,10 @@ def WithTracing(
|
|
|
640
627
|
def _execute_with_span_sync(executor: Callable[[], Any], input_data: Any) -> Any:
|
|
641
628
|
"""Execute sync function within span context, handling input/output and exceptions."""
|
|
642
629
|
# Ensure tracer provider is initialized before creating spans
|
|
643
|
-
get_aiqa_client()
|
|
630
|
+
client = get_aiqa_client()
|
|
631
|
+
if not client.enabled:
|
|
632
|
+
return executor()
|
|
633
|
+
|
|
644
634
|
with tracer.start_as_current_span(fn_name) as span:
|
|
645
635
|
if not _setup_span(span, input_data):
|
|
646
636
|
return executor()
|
|
@@ -656,7 +646,10 @@ def WithTracing(
|
|
|
656
646
|
async def _execute_with_span_async(executor: Callable[[], Any], input_data: Any) -> Any:
|
|
657
647
|
"""Execute async function within span context, handling input/output and exceptions."""
|
|
658
648
|
# Ensure tracer provider is initialized before creating spans
|
|
659
|
-
get_aiqa_client()
|
|
649
|
+
client = get_aiqa_client()
|
|
650
|
+
if not client.enabled:
|
|
651
|
+
return await executor()
|
|
652
|
+
|
|
660
653
|
with tracer.start_as_current_span(fn_name) as span:
|
|
661
654
|
if not _setup_span(span, input_data):
|
|
662
655
|
return await executor()
|
|
@@ -675,7 +668,10 @@ def WithTracing(
|
|
|
675
668
|
def _execute_generator_sync(executor: Callable[[], Any], input_data: Any) -> Any:
|
|
676
669
|
"""Execute sync generator function, returning a traced generator."""
|
|
677
670
|
# Ensure tracer provider is initialized before creating spans
|
|
678
|
-
get_aiqa_client()
|
|
671
|
+
client = get_aiqa_client()
|
|
672
|
+
if not client.enabled:
|
|
673
|
+
return executor()
|
|
674
|
+
|
|
679
675
|
# Create span but don't use 'with' - span will be closed by TracedGenerator
|
|
680
676
|
span = tracer.start_span(fn_name)
|
|
681
677
|
token = trace.context_api.attach(trace.context_api.set_span_in_context(span))
|
|
@@ -698,7 +694,10 @@ def WithTracing(
|
|
|
698
694
|
async def _execute_generator_async(executor: Callable[[], Any], input_data: Any) -> Any:
|
|
699
695
|
"""Execute async generator function, returning a traced async generator."""
|
|
700
696
|
# Ensure tracer provider is initialized before creating spans
|
|
701
|
-
get_aiqa_client()
|
|
697
|
+
client = get_aiqa_client()
|
|
698
|
+
if not client.enabled:
|
|
699
|
+
return await executor()
|
|
700
|
+
|
|
702
701
|
# Create span but don't use 'with' - span will be closed by TracedAsyncGenerator
|
|
703
702
|
span = tracer.start_span(fn_name)
|
|
704
703
|
token = trace.context_api.attach(trace.context_api.set_span_in_context(span))
|
|
@@ -957,16 +956,6 @@ def set_component_tag(tag: str) -> None:
|
|
|
957
956
|
"""
|
|
958
957
|
_set_component_tag(tag)
|
|
959
958
|
|
|
960
|
-
def get_provider() -> Optional[TracerProvider]:
|
|
961
|
-
"""Get the tracer provider for advanced usage."""
|
|
962
|
-
client = get_aiqa_client()
|
|
963
|
-
return client.get("provider")
|
|
964
|
-
|
|
965
|
-
def get_exporter() -> Optional[AIQASpanExporter]:
|
|
966
|
-
"""Get the exporter for advanced usage."""
|
|
967
|
-
client = get_aiqa_client()
|
|
968
|
-
return client.get("exporter")
|
|
969
|
-
|
|
970
959
|
|
|
971
960
|
def get_active_trace_id() -> Optional[str]:
|
|
972
961
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiqa-client
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: OpenTelemetry-based Python client for tracing functions and sending traces to the AIQA server
|
|
5
5
|
Author-email: AIQA <info@aiqa.dev>
|
|
6
6
|
License: MIT
|
|
@@ -67,6 +67,9 @@ export AIQA_SERVER_URL="http://localhost:3000"
|
|
|
67
67
|
export AIQA_API_KEY="your-api-key"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
**Note:** If `AIQA_SERVER_URL` or `AIQA_API_KEY` are not set, tracing will be automatically disabled. You'll see one warning message at the start, and your application will continue to run without tracing.
|
|
71
|
+
You can check if tracing is enabled via `get_aiqa_client().enabled`.
|
|
72
|
+
|
|
70
73
|
## Usage
|
|
71
74
|
|
|
72
75
|
### Basic Usage
|
|
@@ -131,16 +134,40 @@ asyncio.run(main())
|
|
|
131
134
|
To ensure all spans are sent before process exit:
|
|
132
135
|
|
|
133
136
|
```python
|
|
134
|
-
from aiqa import
|
|
137
|
+
from aiqa import flush_tracing
|
|
135
138
|
import asyncio
|
|
136
139
|
|
|
137
140
|
async def main():
|
|
138
141
|
# Your code here
|
|
139
|
-
await
|
|
142
|
+
await flush_tracing()
|
|
140
143
|
|
|
141
144
|
asyncio.run(main())
|
|
142
145
|
```
|
|
143
146
|
|
|
147
|
+
### Enabling/Disabling Tracing
|
|
148
|
+
|
|
149
|
+
You can programmatically enable or disable tracing:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from aiqa import get_aiqa_client
|
|
153
|
+
|
|
154
|
+
client = get_aiqa_client()
|
|
155
|
+
|
|
156
|
+
# Disable tracing (spans won't be created or exported)
|
|
157
|
+
client.enabled = False
|
|
158
|
+
|
|
159
|
+
# Re-enable tracing
|
|
160
|
+
client.enabled = True
|
|
161
|
+
|
|
162
|
+
# Check if tracing is enabled
|
|
163
|
+
if client.enabled:
|
|
164
|
+
print("Tracing is enabled")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
When tracing is disabled:
|
|
168
|
+
- Spans are not created (functions execute normally without tracing overhead)
|
|
169
|
+
- Spans are not exported to the server
|
|
170
|
+
|
|
144
171
|
### Setting Span Attributes and Names
|
|
145
172
|
|
|
146
173
|
```python
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
aiqa/__init__.py,sha256=dnCT31jRL0nUeSvHksUris3_7lxlxChfzshxY7_gHlk,1455
|
|
2
|
+
aiqa/aiqa_exporter.py,sha256=MbA7SkJoNm03dvrcGteU57Y0YNpVw8fzL_W-RI2lI0Q,27698
|
|
3
|
+
aiqa/client.py,sha256=TolaBb7ZnnD5SawclI9KMBsdGKaAosTxvLVeGlKseAA,9599
|
|
4
|
+
aiqa/constants.py,sha256=3QLmyhyVayKebM5N50P1oYbI0LtQmqxTp17UZnUeixc,89
|
|
5
|
+
aiqa/experiment_runner.py,sha256=ZEDwECstAv4lWXpcdB9WSxfDQj43iqkGzB_YzoY933M,12053
|
|
6
|
+
aiqa/object_serialiser.py,sha256=pgcBVw5sZH8f7N6n3-qOvEcbNhuPS5yq7qdhaNT6Sks,15236
|
|
7
|
+
aiqa/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
aiqa/test_experiment_runner.py,sha256=LM8BuCrzBZL0Wyu_ierK0tNLsOUxxMTAHbAGW2G0qp0,5562
|
|
9
|
+
aiqa/test_tracing.py,sha256=mSVrhRQ6Dz5djlSUkCt097sIr84562w6E0BnuQDpMrI,8347
|
|
10
|
+
aiqa/tracing.py,sha256=SsuK6WNgk3LbWt1aQwPPIDhitBmtyU6GOsMRvouXpDw,49706
|
|
11
|
+
aiqa_client-0.3.7.dist-info/licenses/LICENSE,sha256=kIzkzLuzG0HHaWYm4F4W5FeJ1Yxut3Ec6bhLWyw798A,1062
|
|
12
|
+
aiqa_client-0.3.7.dist-info/METADATA,sha256=LSshQdYneT3PfZIy19TwBt6Ow8L7IlJYHmx3EPH32pg,7505
|
|
13
|
+
aiqa_client-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
aiqa_client-0.3.7.dist-info/top_level.txt,sha256=nwcsuVVSuWu27iLxZd4n1evVzv1W6FVTrSnCXCc-NQs,5
|
|
15
|
+
aiqa_client-0.3.7.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
aiqa/__init__.py,sha256=MIlSmkWRtoRbLvjShCnk1jPC54Y5kCqwojYJQjEFvok,1577
|
|
2
|
-
aiqa/aiqa_exporter.py,sha256=ZfkIwh8H1mgBZ7fJtM_RhuancCAgcnNuEWsADib4rsc,26806
|
|
3
|
-
aiqa/client.py,sha256=wE6EsypbTfp3Cz39IhEycEVT0IZGdJz7yQvtZ15qKJo,6364
|
|
4
|
-
aiqa/experiment_runner.py,sha256=ZEDwECstAv4lWXpcdB9WSxfDQj43iqkGzB_YzoY933M,12053
|
|
5
|
-
aiqa/object_serialiser.py,sha256=pgcBVw5sZH8f7N6n3-qOvEcbNhuPS5yq7qdhaNT6Sks,15236
|
|
6
|
-
aiqa/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
aiqa/test_experiment_runner.py,sha256=LM8BuCrzBZL0Wyu_ierK0tNLsOUxxMTAHbAGW2G0qp0,5562
|
|
8
|
-
aiqa/test_tracing.py,sha256=mSVrhRQ6Dz5djlSUkCt097sIr84562w6E0BnuQDpMrI,8347
|
|
9
|
-
aiqa/tracing.py,sha256=xVzrmx8uFEBOVfBakzVrrGeCxxZWCLOr_vXHiJmBiaw,50118
|
|
10
|
-
aiqa_client-0.3.5.dist-info/licenses/LICENSE,sha256=kIzkzLuzG0HHaWYm4F4W5FeJ1Yxut3Ec6bhLWyw798A,1062
|
|
11
|
-
aiqa_client-0.3.5.dist-info/METADATA,sha256=VTGqOaTGy25g0gxwJ-tiJwK484ns7qW0cDsNNa7dUwQ,6726
|
|
12
|
-
aiqa_client-0.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
aiqa_client-0.3.5.dist-info/top_level.txt,sha256=nwcsuVVSuWu27iLxZd4n1evVzv1W6FVTrSnCXCc-NQs,5
|
|
14
|
-
aiqa_client-0.3.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|