Skip to content

Commit 49ac012

Browse files
authored
Feature/extra quotient spanattributes (#110)
* EOT marker * detections array attr * rename * clean
1 parent a3a224b commit 49ac012

File tree

3 files changed

+49
-14
lines changed

3 files changed

+49
-14
lines changed

quotientai/async_client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,19 +445,24 @@ def __init__(self, tracing_resource):
445445
self.app_name: Optional[str] = None
446446
self.environment: Optional[str] = None
447447
self.instruments: Optional[list] = None
448+
self.detections: Optional[List[str]] = None
448449
self._configured = False
449450

450-
def init(self, app_name: str, environment: str, instruments: Optional[list] = None):
451+
def init(self, app_name: str, environment: str, instruments: Optional[list] = None, detections: Optional[List[str]] = None):
451452
"""
452453
Configure the tracer with the provided parameters and return self.
453454
This method must be called before using trace().
454455
"""
455456
self.app_name = app_name
456457
self.environment = environment
457458
self.instruments = instruments
459+
self.detections = detections
458460

459461
self.tracing_resource.configure(
460-
app_name=app_name, environment=environment, instruments=instruments
462+
app_name=app_name,
463+
environment=environment,
464+
instruments=instruments,
465+
detections=detections
461466
)
462467

463468
self._configured = True

quotientai/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ def __init__(self, tracing_resource: TracingResource):
461461
self.environment: Optional[str] = None
462462
self.metadata: Optional[Dict[str, Any]] = None
463463
self.instruments: Optional[List[Any]] = None
464-
464+
self.detections: Optional[List[str]] = None
465465
self._configured = False
466466

467467
def init(
@@ -470,6 +470,7 @@ def init(
470470
app_name: str,
471471
environment: str,
472472
instruments: Optional[List[Any]] = None,
473+
detections: Optional[List[str]] = None,
473474
) -> "QuotientTracer":
474475
"""
475476
Configure the tracer with the provided parameters and return self.
@@ -478,12 +479,13 @@ def init(
478479
self.app_name = app_name
479480
self.environment = environment
480481
self.instruments = instruments
481-
482+
self.detections = detections
482483
# Configure the underlying tracing resource
483484
self.tracing_resource.configure(
484485
app_name=app_name,
485486
environment=environment,
486487
instruments=instruments,
488+
detections=detections,
487489
)
488490

489491
self._configured = True

quotientai/tracing/core.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import atexit
77
import weakref
8-
8+
import time
99
from enum import Enum
1010
from typing import Optional
1111

@@ -40,6 +40,7 @@ def start_span(name: str):
4040
class QuotientAttributes(str, Enum):
4141
app_name = "app.name"
4242
environment = "app.environment"
43+
detections = "quotient.detections"
4344

4445

4546
class QuotientAttributesSpanProcessor(SpanProcessor):
@@ -54,17 +55,22 @@ class QuotientAttributesSpanProcessor(SpanProcessor):
5455

5556
app_name: str
5657
environment: str
58+
detections: str
5759

58-
def __init__(self, app_name: str, environment: str):
60+
def __init__(self, app_name: str, environment: str, detections: Optional[str] = None):
5961
self.app_name = app_name
6062
self.environment = environment
63+
self.detections = detections
6164

6265
def on_start(self, span: Span, parent_context: Optional[otel_context.Context] = None) -> None:
6366
attributes = {
6467
QuotientAttributes.app_name: self.app_name,
6568
QuotientAttributes.environment: self.environment,
6669
}
6770

71+
if self.detections is not None:
72+
attributes[QuotientAttributes.detections] = self.detections
73+
6874
span.set_attributes(attributes)
6975
super().on_start(span, parent_context)
7076

@@ -78,10 +84,10 @@ def __init__(self, client):
7884
self._app_name = None
7985
self._environment = None
8086
self._instruments = None
81-
87+
self._detections = None
8288
atexit.register(self._cleanup)
8389

84-
def configure(self, app_name: str, environment: str, instruments: Optional[list] = None):
90+
def configure(self, app_name: str, environment: str, instruments: Optional[list] = None, detections: Optional[list] = None):
8591
"""
8692
Configure the tracing resource with app_name, environment, and instruments.
8793
This allows the trace decorator to be used without parameters.
@@ -100,14 +106,16 @@ def configure(self, app_name: str, environment: str, instruments: Optional[list]
100106
self._app_name = app_name
101107
self._environment = environment
102108
self._instruments = instruments
109+
self._detections = ",".join(detections) if detections else None
103110

104-
def init(self, app_name: str, environment: str, instruments: Optional[list] = None):
111+
def init(self, app_name: str, environment: str, instruments: Optional[list] = None, detections: Optional[list] = None):
105112
"""
106113
Initialize tracing with app_name, environment, and instruments.
107114
This is a convenience method that calls configure and then sets up the collector.
108115
"""
109-
self.configure(app_name, environment, instruments)
110-
self._setup_auto_collector(app_name, environment, instruments)
116+
self.configure(app_name, environment, instruments, detections)
117+
detections_str = ",".join(detections) if detections else None
118+
self._setup_auto_collector(app_name, environment, instruments, detections_str)
111119

112120
def get_vector_db_instrumentors(self):
113121
"""
@@ -147,7 +155,7 @@ def _create_otlp_exporter(self, endpoint: str, headers: dict):
147155
return OTLPSpanExporter(endpoint=endpoint, headers=headers)
148156

149157
@functools.lru_cache()
150-
def _setup_auto_collector(self, app_name: str, environment: str, instruments: Optional[list] = None):
158+
def _setup_auto_collector(self, app_name: str, environment: str, instruments: Optional[tuple] = None, detections: Optional[str] = None):
151159
"""
152160
Automatically setup OTLP exporter to send traces to collector
153161
"""
@@ -186,6 +194,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op
186194
quotient_attributes_span_processor = QuotientAttributesSpanProcessor(
187195
app_name=app_name,
188196
environment=environment,
197+
detections=detections,
189198
)
190199
tracer_provider.add_span_processor(quotient_attributes_span_processor)
191200
tracer_provider.add_span_processor(span_processor)
@@ -235,18 +244,22 @@ def sync_func_wrapper(*args, **kwargs):
235244
app_name=self._app_name,
236245
environment=self._environment,
237246
instruments=tuple(self._instruments) if self._instruments is not None else None,
247+
detections=self._detections,
238248
)
239249

240250
# if there is no tracer, just run the function normally
241251
if self.tracer is None:
242252
return func(*args, **kwargs)
243253

244-
with self.tracer.start_as_current_span(name):
254+
with self.tracer.start_as_current_span(name) as root_span:
245255
try:
246256
result = func(*args, **kwargs)
247257
except Exception as e:
248258
raise e
249259
finally:
260+
trace_id = root_span.get_span_context().trace_id
261+
self._create_end_of_trace_span(trace_id)
262+
250263
# here we can log the call once we have the result.
251264
# TODO: add otel support for quotient logging
252265
pass
@@ -259,17 +272,21 @@ async def async_func_wrapper(*args, **kwargs):
259272
app_name=self._app_name,
260273
environment=self._environment,
261274
instruments=tuple(self._instruments) if self._instruments is not None else None,
275+
detections=self._detections,
262276
)
263277

264278
if self.tracer is None:
265279
return await func(*args, **kwargs)
266280

267-
with self.tracer.start_as_current_span(name):
281+
with self.tracer.start_as_current_span(name) as root_span:
268282
try:
269283
result = await func(*args, **kwargs)
270284
except Exception as e:
271285
raise e
272286
finally:
287+
trace_id = root_span.get_span_context().trace_id
288+
self._create_end_of_trace_span(trace_id)
289+
273290
# here we can log the call once we have the result.
274291
# TODO: add otel support for quotient logging
275292
pass
@@ -316,3 +333,14 @@ def force_flush(self):
316333
logger.info("Forced flush of pending spans")
317334
except Exception as e:
318335
logger.error(f"Failed to force flush spans: {str(e)}")
336+
337+
def _create_end_of_trace_span(self, trace_id):
338+
"""Create an end-of-trace marker span"""
339+
try:
340+
with self.tracer.start_as_current_span("quotient.end_of_trace") as span:
341+
span.set_attribute("quotient.trace.complete", True)
342+
span.set_attribute("quotient.trace.marker", True)
343+
span.set_attribute("quotient.trace.id", format(trace_id, '032x'))
344+
span.set_attribute("quotient.marker.timestamp", time.time_ns())
345+
except Exception as _:
346+
pass

0 commit comments

Comments
 (0)