traccia 0.1.2__py3-none-any.whl → 0.1.6__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.
Files changed (57) hide show
  1. traccia/__init__.py +73 -0
  2. traccia/auto.py +748 -0
  3. traccia/auto_instrumentation.py +74 -0
  4. traccia/cli.py +349 -0
  5. traccia/config.py +699 -0
  6. traccia/context/__init__.py +33 -0
  7. traccia/context/context.py +67 -0
  8. traccia/context/propagators.py +283 -0
  9. traccia/errors.py +48 -0
  10. traccia/exporter/__init__.py +8 -0
  11. traccia/exporter/console_exporter.py +31 -0
  12. traccia/exporter/file_exporter.py +178 -0
  13. traccia/exporter/http_exporter.py +214 -0
  14. traccia/exporter/otlp_exporter.py +190 -0
  15. traccia/instrumentation/__init__.py +26 -0
  16. traccia/instrumentation/anthropic.py +92 -0
  17. traccia/instrumentation/decorator.py +263 -0
  18. traccia/instrumentation/fastapi.py +38 -0
  19. traccia/instrumentation/http_client.py +21 -0
  20. traccia/instrumentation/http_server.py +25 -0
  21. traccia/instrumentation/openai.py +358 -0
  22. traccia/instrumentation/requests.py +68 -0
  23. traccia/integrations/__init__.py +39 -0
  24. traccia/integrations/langchain/__init__.py +14 -0
  25. traccia/integrations/langchain/callback.py +418 -0
  26. traccia/integrations/langchain/utils.py +129 -0
  27. traccia/integrations/openai_agents/__init__.py +73 -0
  28. traccia/integrations/openai_agents/processor.py +262 -0
  29. traccia/pricing_config.py +58 -0
  30. traccia/processors/__init__.py +35 -0
  31. traccia/processors/agent_enricher.py +159 -0
  32. traccia/processors/batch_processor.py +140 -0
  33. traccia/processors/cost_engine.py +71 -0
  34. traccia/processors/cost_processor.py +70 -0
  35. traccia/processors/drop_policy.py +44 -0
  36. traccia/processors/logging_processor.py +31 -0
  37. traccia/processors/rate_limiter.py +223 -0
  38. traccia/processors/sampler.py +22 -0
  39. traccia/processors/token_counter.py +216 -0
  40. traccia/runtime_config.py +127 -0
  41. traccia/tracer/__init__.py +15 -0
  42. traccia/tracer/otel_adapter.py +577 -0
  43. traccia/tracer/otel_utils.py +24 -0
  44. traccia/tracer/provider.py +155 -0
  45. traccia/tracer/span.py +286 -0
  46. traccia/tracer/span_context.py +16 -0
  47. traccia/tracer/tracer.py +243 -0
  48. traccia/utils/__init__.py +19 -0
  49. traccia/utils/helpers.py +95 -0
  50. {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/METADATA +72 -15
  51. traccia-0.1.6.dist-info/RECORD +55 -0
  52. traccia-0.1.6.dist-info/top_level.txt +1 -0
  53. traccia-0.1.2.dist-info/RECORD +0 -6
  54. traccia-0.1.2.dist-info/top_level.txt +0 -1
  55. {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/WHEEL +0 -0
  56. {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/entry_points.txt +0 -0
  57. {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,577 @@
1
+ """Adapter layer wrapping OpenTelemetry components to match Traccia API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ import traceback
7
+ from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
8
+
9
+ from opentelemetry import trace as otel_trace_api
10
+ from opentelemetry.sdk.trace import TracerProvider as OTelTracerProvider
11
+ from opentelemetry.sdk.trace import ReadableSpan
12
+ from opentelemetry.sdk.resources import Resource as OTelResource
13
+ from opentelemetry.trace import Status as OTelStatus, StatusCode as OTelStatusCode
14
+ from opentelemetry.trace import SpanContext as OTelSpanContext
15
+ from opentelemetry.trace import Span as OTelSpan, NonRecordingSpan
16
+
17
+ from traccia.tracer.otel_utils import (
18
+ otel_trace_id_to_traccia,
19
+ otel_span_id_to_traccia,
20
+ traccia_id_to_otel_trace_id,
21
+ traccia_id_to_otel_span_id,
22
+ otel_timestamp_to_ns,
23
+ )
24
+ from traccia.tracer.span_context import SpanContext as TracciaSpanContext
25
+ from traccia.tracer.span import SpanStatus as TracciaSpanStatus
26
+
27
+ if TYPE_CHECKING:
28
+ from traccia.tracer.tracer import Tracer as TracciaTracer
29
+
30
+
31
+ class TracciaSpanContextAdapter:
32
+ """Adapter wrapping OpenTelemetry SpanContext to match Traccia SpanContext API."""
33
+
34
+ def __init__(self, otel_context: OTelSpanContext):
35
+ """
36
+ Initialize adapter with OpenTelemetry SpanContext.
37
+
38
+ Args:
39
+ otel_context: OpenTelemetry SpanContext instance
40
+ """
41
+ self._otel_context = otel_context
42
+ # Convert int IDs to hex strings for Traccia compatibility
43
+ self.trace_id = otel_trace_id_to_traccia(otel_context.trace_id)
44
+ self.span_id = otel_span_id_to_traccia(otel_context.span_id)
45
+ self.trace_flags = otel_context.trace_flags.sampled if hasattr(otel_context.trace_flags, 'sampled') else (1 if otel_context.is_valid else 0)
46
+ # Convert TraceState to string
47
+ trace_state = otel_context.trace_state
48
+ if trace_state:
49
+ # Convert OTel TraceState to W3C format string
50
+ items = []
51
+ for key, value in trace_state.items():
52
+ items.append(f"{key}={value}")
53
+ self.trace_state = ",".join(items) if items else None
54
+ else:
55
+ self.trace_state = None
56
+
57
+ def is_valid(self) -> bool:
58
+ """Check if the span context is valid."""
59
+ return self._otel_context.is_valid
60
+
61
+ @staticmethod
62
+ def from_traccia(traccia_context: TracciaSpanContext) -> OTelSpanContext:
63
+ """
64
+ Convert Traccia SpanContext to OpenTelemetry SpanContext.
65
+
66
+ Args:
67
+ traccia_context: Traccia SpanContext instance
68
+
69
+ Returns:
70
+ OpenTelemetry SpanContext
71
+ """
72
+ from opentelemetry.trace import TraceFlags, TraceState
73
+
74
+ trace_id = traccia_id_to_otel_trace_id(traccia_context.trace_id)
75
+ span_id = traccia_id_to_otel_span_id(traccia_context.span_id)
76
+ trace_flags = TraceFlags(traccia_context.trace_flags)
77
+
78
+ # Parse trace_state string to OTel TraceState
79
+ trace_state = None
80
+ if traccia_context.trace_state:
81
+ # Parse W3C format: key1=value1,key2=value2
82
+ items = []
83
+ for pair in traccia_context.trace_state.split(','):
84
+ if '=' in pair:
85
+ key, value = pair.split('=', 1)
86
+ items.append((key.strip(), value.strip()))
87
+ if items:
88
+ trace_state = TraceState(items)
89
+
90
+ return OTelSpanContext(
91
+ trace_id=trace_id,
92
+ span_id=span_id,
93
+ is_remote=False,
94
+ trace_flags=trace_flags,
95
+ trace_state=trace_state or TraceState(),
96
+ )
97
+
98
+
99
+ class TracciaSpanAdapter:
100
+ """Adapter wrapping OpenTelemetry Span to match Traccia Span API."""
101
+
102
+ def __init__(
103
+ self,
104
+ otel_span: OTelSpan,
105
+ tracer: "TracciaTracerAdapter",
106
+ parent_span_id: Optional[str] = None,
107
+ ):
108
+ """
109
+ Initialize adapter with OpenTelemetry Span.
110
+
111
+ Args:
112
+ otel_span: OpenTelemetry Span instance
113
+ tracer: Traccia TracerAdapter that created this span
114
+ parent_span_id: Parent span ID in hex string format
115
+ """
116
+ self._otel_span = otel_span
117
+ self.tracer = tracer
118
+ self.parent_span_id = parent_span_id
119
+
120
+ # Get span context
121
+ otel_context = otel_span.get_span_context()
122
+ self.context = TracciaSpanContextAdapter(otel_context)
123
+
124
+ # Get name
125
+ if isinstance(otel_span, ReadableSpan):
126
+ self.name = otel_span.name
127
+ else:
128
+ # For non-readable spans, we need to track name separately
129
+ self.name = getattr(otel_span, '_traccia_name', 'unknown')
130
+
131
+ # Initialize attributes dict - will be kept in sync with OTel span
132
+ self.attributes: Dict[str, Any] = {}
133
+ if isinstance(otel_span, ReadableSpan):
134
+ if otel_span.attributes:
135
+ self.attributes = dict(otel_span.attributes)
136
+
137
+ # Initialize events list - will be kept in sync
138
+ self.events: List[Dict[str, Any]] = []
139
+ if isinstance(otel_span, ReadableSpan):
140
+ if otel_span.events:
141
+ for event in otel_span.events:
142
+ self.events.append({
143
+ "name": event.name,
144
+ "attributes": dict(event.attributes) if event.attributes else {},
145
+ "timestamp_ns": event.timestamp,
146
+ })
147
+
148
+ # Convert status
149
+ if isinstance(otel_span, ReadableSpan):
150
+ self.status = self._convert_status(otel_span.status)
151
+ self.status_description = otel_span.status.description if otel_span.status else None
152
+ else:
153
+ self.status = TracciaSpanStatus.UNSET
154
+ self.status_description = None
155
+
156
+ # Get timestamps
157
+ if isinstance(otel_span, ReadableSpan):
158
+ self.start_time_ns = otel_span.start_time if otel_span.start_time else time.time_ns()
159
+ self.end_time_ns = otel_span.end_time if otel_span.end_time else None
160
+ else:
161
+ self.start_time_ns = time.time_ns()
162
+ self.end_time_ns = None
163
+
164
+ self._activation_tokens: Optional[Tuple] = None
165
+ self._ended = False
166
+
167
+ # Apply tracestate enrichment (matching Traccia behavior)
168
+ self._enrich_tracestate()
169
+
170
+ def _enrich_tracestate(self) -> None:
171
+ """Enrich tracestate with runtime metadata (tenant, project, debug)."""
172
+ try:
173
+ from traccia.context.propagators import format_tracestate, parse_tracestate
174
+ from traccia import runtime_config
175
+
176
+ base = parse_tracestate(self.context.trace_state or "")
177
+ if runtime_config.get_tenant_id():
178
+ base.setdefault("tenant", runtime_config.get_tenant_id())
179
+ if runtime_config.get_project_id():
180
+ base.setdefault("project", runtime_config.get_project_id())
181
+ if runtime_config.get_debug():
182
+ base.setdefault("dbg", "1")
183
+
184
+ ts = format_tracestate(base)
185
+ if ts:
186
+ # Update the context
187
+ self.context.trace_state = ts
188
+ except Exception:
189
+ pass
190
+
191
+ @staticmethod
192
+ def _convert_status(otel_status: OTelStatus) -> TracciaSpanStatus:
193
+ """Convert OpenTelemetry Status to Traccia SpanStatus."""
194
+ if not otel_status:
195
+ return TracciaSpanStatus.UNSET
196
+
197
+ if otel_status.status_code == OTelStatusCode.OK:
198
+ return TracciaSpanStatus.OK
199
+ elif otel_status.status_code == OTelStatusCode.ERROR:
200
+ return TracciaSpanStatus.ERROR
201
+ else:
202
+ return TracciaSpanStatus.UNSET
203
+
204
+ @staticmethod
205
+ def _convert_status_to_otel(traccia_status: TracciaSpanStatus, description: Optional[str] = None) -> OTelStatus:
206
+ """Convert Traccia SpanStatus to OpenTelemetry Status."""
207
+ if traccia_status == TracciaSpanStatus.OK:
208
+ return OTelStatus(status_code=OTelStatusCode.OK, description=description)
209
+ elif traccia_status == TracciaSpanStatus.ERROR:
210
+ return OTelStatus(status_code=OTelStatusCode.ERROR, description=description)
211
+ else:
212
+ return OTelStatus(status_code=OTelStatusCode.UNSET, description=description)
213
+
214
+ @property
215
+ def duration_ns(self) -> Optional[int]:
216
+ """Get span duration in nanoseconds."""
217
+ if self.end_time_ns is None:
218
+ return None
219
+ return self.end_time_ns - self.start_time_ns
220
+
221
+ def set_attribute(self, key: str, value: Any) -> None:
222
+ """Set an attribute on the span."""
223
+ # Only set on OTel span if it's not ended
224
+ # OTel spans don't allow setting attributes after end, but Traccia does
225
+ # We'll allow it for Traccia compatibility but only update our local dict
226
+ try:
227
+ if not self._ended:
228
+ self._otel_span.set_attribute(key, value)
229
+ except Exception:
230
+ # Span may be ended, just update local dict
231
+ pass
232
+ self.attributes[key] = value
233
+
234
+ def add_event(
235
+ self,
236
+ name: str,
237
+ attributes: Optional[Dict[str, Any]] = None,
238
+ timestamp_ns: Optional[int] = None,
239
+ ) -> None:
240
+ """Add an event to the span."""
241
+ event_dict = {
242
+ "name": name,
243
+ "attributes": dict(attributes) if attributes else {},
244
+ "timestamp_ns": timestamp_ns or time.time_ns(),
245
+ }
246
+ self.events.append(event_dict)
247
+
248
+ # Add to OTel span
249
+ self._otel_span.add_event(
250
+ name=name,
251
+ attributes=attributes,
252
+ timestamp=timestamp_ns,
253
+ )
254
+
255
+ def record_exception(self, error: BaseException) -> None:
256
+ """Record an exception event on the span."""
257
+ # Add to Traccia events list
258
+ self.add_event(
259
+ "exception",
260
+ {
261
+ "exception.type": error.__class__.__name__,
262
+ "exception.message": str(error),
263
+ "exception.stacktrace": "".join(
264
+ traceback.format_exception(error.__class__, error, error.__traceback__)
265
+ ),
266
+ },
267
+ )
268
+
269
+ # Record on OTel span
270
+ self._otel_span.record_exception(error)
271
+
272
+ # Set error status
273
+ self.set_status(TracciaSpanStatus.ERROR, str(error))
274
+
275
+ def set_status(self, status: TracciaSpanStatus, description: Optional[str] = None) -> None:
276
+ """Set the span status."""
277
+ self.status = status
278
+ self.status_description = description
279
+
280
+ # Convert and set on OTel span
281
+ otel_status = self._convert_status_to_otel(status, description)
282
+ self._otel_span.set_status(otel_status)
283
+
284
+ def end(self) -> None:
285
+ """End the span."""
286
+ if self._ended:
287
+ return
288
+
289
+ self.end_time_ns = time.time_ns()
290
+ if self.status == TracciaSpanStatus.UNSET:
291
+ self.status = TracciaSpanStatus.OK
292
+
293
+ self._ended = True
294
+
295
+ # End OTel span
296
+ self._otel_span.end(end_time=self.end_time_ns)
297
+
298
+ # Notify tracer's provider
299
+ self.tracer._on_span_end(self)
300
+
301
+ def __enter__(self) -> "TracciaSpanAdapter":
302
+ """Enter context manager."""
303
+ self._activation_tokens = self.tracer._activate_span(self)
304
+ return self
305
+
306
+ def __exit__(self, exc_type, exc, tb) -> bool:
307
+ """Exit context manager."""
308
+ try:
309
+ if exc:
310
+ self.record_exception(exc)
311
+ self.end()
312
+ finally:
313
+ if self._activation_tokens:
314
+ self.tracer._deactivate_span(self._activation_tokens)
315
+ self._activation_tokens = None
316
+ return False
317
+
318
+ async def __aenter__(self) -> "TracciaSpanAdapter":
319
+ """Enter async context manager."""
320
+ self._activation_tokens = self.tracer._activate_span(self)
321
+ return self
322
+
323
+ async def __aexit__(self, exc_type, exc, tb) -> bool:
324
+ """Exit async context manager."""
325
+ try:
326
+ if exc:
327
+ self.record_exception(exc)
328
+ self.end()
329
+ finally:
330
+ if self._activation_tokens:
331
+ self.tracer._deactivate_span(self._activation_tokens)
332
+ self._activation_tokens = None
333
+ return False
334
+
335
+
336
+ class TracciaTracerAdapter:
337
+ """Adapter wrapping OpenTelemetry Tracer to match Traccia Tracer API."""
338
+
339
+ def __init__(self, otel_tracer: otel_trace_api.Tracer, provider: "TracciaTracerProviderAdapter", instrumentation_scope: str):
340
+ """
341
+ Initialize adapter with OpenTelemetry Tracer.
342
+
343
+ Args:
344
+ otel_tracer: OpenTelemetry Tracer instance
345
+ provider: Traccia TracerProviderAdapter that created this tracer
346
+ instrumentation_scope: Instrumentation scope name
347
+ """
348
+ self._otel_tracer = otel_tracer
349
+ self._provider = provider
350
+ self.instrumentation_scope = instrumentation_scope
351
+
352
+ def start_span(
353
+ self,
354
+ name: str,
355
+ attributes: Optional[Dict[str, Any]] = None,
356
+ parent: Optional["TracciaSpanAdapter"] = None,
357
+ parent_context: Optional[TracciaSpanContext] = None,
358
+ ) -> TracciaSpanAdapter:
359
+ """
360
+ Start a new span.
361
+
362
+ Args:
363
+ name: Span name
364
+ attributes: Optional attributes dictionary
365
+ parent: Optional parent Traccia span
366
+ parent_context: Optional parent Traccia span context
367
+
368
+ Returns:
369
+ TracciaSpanAdapter wrapping the new OTel span
370
+ """
371
+ from traccia.context import context as span_context
372
+
373
+ # Determine parent
374
+ parent_span = parent or span_context.get_current_span()
375
+
376
+ # Convert parent context if provided
377
+ otel_parent_context = None
378
+ parent_span_id = None
379
+
380
+ if parent_span:
381
+ # Use parent span's context
382
+ parent_span_id = parent_span.context.span_id
383
+ if hasattr(parent_span, '_otel_span'):
384
+ otel_parent_context = otel_trace_api.set_span_in_context(parent_span._otel_span)
385
+ else:
386
+ # Parent is native Traccia span, convert its context
387
+ otel_parent_ctx = TracciaSpanContextAdapter.from_traccia(parent_span.context)
388
+ otel_parent_context = otel_trace_api.set_span_in_context(
389
+ NonRecordingSpan(otel_parent_ctx)
390
+ )
391
+ elif parent_context and parent_context.is_valid():
392
+ # Convert provided parent context
393
+ parent_span_id = parent_context.span_id
394
+ otel_parent_ctx = TracciaSpanContextAdapter.from_traccia(parent_context)
395
+ otel_parent_context = otel_trace_api.set_span_in_context(
396
+ NonRecordingSpan(otel_parent_ctx)
397
+ )
398
+
399
+ # Start OTel span
400
+ otel_span = self._otel_tracer.start_span(
401
+ name=name,
402
+ attributes=attributes,
403
+ context=otel_parent_context,
404
+ )
405
+
406
+ # Store name for non-readable spans
407
+ if not isinstance(otel_span, ReadableSpan):
408
+ otel_span._traccia_name = name
409
+
410
+ # Wrap in adapter
411
+ return TracciaSpanAdapter(otel_span, self, parent_span_id)
412
+
413
+ def start_as_current_span(
414
+ self,
415
+ name: str,
416
+ attributes: Optional[Dict[str, Any]] = None,
417
+ parent: Optional["TracciaSpanAdapter"] = None,
418
+ parent_context: Optional[TracciaSpanContext] = None,
419
+ ) -> TracciaSpanAdapter:
420
+ """
421
+ Start a span and set it as current.
422
+
423
+ Args:
424
+ name: Span name
425
+ attributes: Optional attributes dictionary
426
+ parent: Optional parent Traccia span
427
+ parent_context: Optional parent Traccia span context
428
+
429
+ Returns:
430
+ TracciaSpanAdapter wrapping the new OTel span
431
+ """
432
+ return self.start_span(
433
+ name=name,
434
+ attributes=attributes,
435
+ parent=parent,
436
+ parent_context=parent_context,
437
+ )
438
+
439
+ def get_current_span(self) -> Optional["TracciaSpanAdapter"]:
440
+ """Get the current span."""
441
+ from traccia.context import context as span_context
442
+ return span_context.get_current_span()
443
+
444
+ def _activate_span(self, span: "TracciaSpanAdapter"):
445
+ """Activate a span (set as current)."""
446
+ from traccia.context import context as span_context
447
+ return span_context.push_span(span)
448
+
449
+ def _deactivate_span(self, tokens) -> None:
450
+ """Deactivate a span (restore previous)."""
451
+ from traccia.context import context as span_context
452
+ span_context.pop_span(tokens)
453
+
454
+ def _on_span_end(self, span: "TracciaSpanAdapter") -> None:
455
+ """Called when a span ends."""
456
+ self._provider._notify_span_end(span)
457
+
458
+
459
+ class TracciaTracerProviderAdapter:
460
+ """Adapter wrapping OpenTelemetry TracerProvider to match Traccia TracerProvider API."""
461
+
462
+ def __init__(self, resource: Optional[Dict[str, str]] = None):
463
+ """
464
+ Initialize adapter with OpenTelemetry TracerProvider.
465
+
466
+ Args:
467
+ resource: Optional resource attributes dictionary
468
+ """
469
+ # Convert resource dict to OTel Resource
470
+ otel_resource = OTelResource.create(resource or {})
471
+ self._otel_provider = OTelTracerProvider(resource=otel_resource)
472
+
473
+ # Store resource as dict for Traccia compatibility
474
+ self.resource = resource or {}
475
+
476
+ # Tracers cache
477
+ self._tracers: Dict[str, TracciaTracerAdapter] = {}
478
+ self._span_processors: List[Any] = []
479
+ self.sampler: Optional[Any] = None
480
+
481
+ def get_tracer(self, name: str) -> TracciaTracerAdapter:
482
+ """
483
+ Get a tracer by name.
484
+
485
+ Args:
486
+ name: Instrumentation scope name
487
+
488
+ Returns:
489
+ TracciaTracerAdapter wrapping OTel Tracer
490
+ """
491
+ if name in self._tracers:
492
+ return self._tracers[name]
493
+
494
+ # Get OTel tracer
495
+ otel_tracer = self._otel_provider.get_tracer(name)
496
+
497
+ # Wrap in adapter
498
+ tracer = TracciaTracerAdapter(otel_tracer, self, name)
499
+ self._tracers[name] = tracer
500
+ return tracer
501
+
502
+ def add_span_processor(self, processor: Any) -> None:
503
+ """
504
+ Add a span processor.
505
+
506
+ Args:
507
+ processor: Traccia SpanProcessor instance
508
+ """
509
+ self._span_processors.append(processor)
510
+
511
+ # If processor has OTel compatibility, add to OTel provider
512
+ # For now, we'll handle Traccia processors via _notify_span_end
513
+
514
+ def set_sampler(self, sampler: Any) -> None:
515
+ """
516
+ Set the sampler.
517
+
518
+ Args:
519
+ sampler: Traccia Sampler instance
520
+ """
521
+ self.sampler = sampler
522
+ # Note: OTel sampler is set at provider creation time
523
+ # For dynamic sampler changes, we'd need to recreate the provider
524
+
525
+ def get_sampler(self) -> Optional[Any]:
526
+ """Get the current sampler."""
527
+ return self.sampler
528
+
529
+ def _notify_span_end(self, span: TracciaSpanAdapter) -> None:
530
+ """
531
+ Notify processors that a span has ended.
532
+
533
+ Args:
534
+ span: The span that ended
535
+ """
536
+ for processor in list(self._span_processors):
537
+ try:
538
+ processor.on_end(span)
539
+ except Exception:
540
+ # Processors should not crash tracing
541
+ continue
542
+
543
+ def force_flush(self, timeout: Optional[float] = None) -> None:
544
+ """Force flush all processors."""
545
+ # Flush OTel provider
546
+ self._otel_provider.force_flush(timeout_millis=int(timeout * 1000) if timeout else 30000)
547
+
548
+ # Flush Traccia processors
549
+ for processor in list(self._span_processors):
550
+ try:
551
+ processor.force_flush(timeout=timeout)
552
+ except Exception:
553
+ continue
554
+
555
+ def shutdown(self) -> None:
556
+ """Shutdown the provider and all processors."""
557
+ # Shutdown OTel provider
558
+ self._otel_provider.shutdown()
559
+
560
+ # Shutdown Traccia processors
561
+ for processor in list(self._span_processors):
562
+ try:
563
+ processor.shutdown()
564
+ except Exception:
565
+ continue
566
+
567
+ @staticmethod
568
+ def generate_trace_id() -> str:
569
+ """Generate a new trace ID in Traccia format (hex string)."""
570
+ import secrets
571
+ return secrets.token_hex(16)
572
+
573
+ @staticmethod
574
+ def generate_span_id() -> str:
575
+ """Generate a new span ID in Traccia format (hex string)."""
576
+ import secrets
577
+ return secrets.token_hex(8)
@@ -0,0 +1,24 @@
1
+ """DEPRECATED: Use traccia.utils.helpers instead.
2
+
3
+ This file is kept for backward compatibility with old adapter code.
4
+ All new code should use traccia.utils.helpers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ # Re-export from utils.helpers for backward compatibility
10
+ from traccia.utils.helpers import (
11
+ format_trace_id as otel_trace_id_to_traccia,
12
+ format_span_id as otel_span_id_to_traccia,
13
+ parse_trace_id as traccia_id_to_otel_trace_id,
14
+ parse_span_id as traccia_id_to_otel_span_id,
15
+ )
16
+
17
+ # Timestamp functions (passthrough)
18
+ def otel_timestamp_to_ns(otel_time: int) -> int:
19
+ """Convert OpenTelemetry timestamp to nanoseconds (passthrough)."""
20
+ return otel_time
21
+
22
+ def ns_to_otel_timestamp(ns: int) -> int:
23
+ """Convert nanoseconds to OpenTelemetry timestamp (passthrough)."""
24
+ return ns