collibra-connector 1.0.19__py3-none-any.whl → 1.1.1__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.
- collibra_connector/__init__.py +284 -4
- collibra_connector/api/Asset.py +301 -3
- collibra_connector/api/Attribute.py +204 -0
- collibra_connector/api/Base.py +2 -2
- collibra_connector/api/Relation.py +216 -0
- collibra_connector/api/Responsibility.py +5 -5
- collibra_connector/api/Search.py +102 -0
- collibra_connector/api/Workflow.py +50 -16
- collibra_connector/api/__init__.py +23 -13
- collibra_connector/async_connector.py +930 -0
- collibra_connector/cli.py +597 -0
- collibra_connector/connector.py +270 -48
- collibra_connector/helpers.py +845 -0
- collibra_connector/lineage.py +716 -0
- collibra_connector/models.py +897 -0
- collibra_connector/py.typed +0 -0
- collibra_connector/telemetry.py +576 -0
- collibra_connector/testing.py +806 -0
- collibra_connector-1.1.1.dist-info/METADATA +540 -0
- collibra_connector-1.1.1.dist-info/RECORD +32 -0
- {collibra_connector-1.0.19.dist-info → collibra_connector-1.1.1.dist-info}/WHEEL +1 -1
- collibra_connector-1.1.1.dist-info/entry_points.txt +2 -0
- collibra_connector-1.0.19.dist-info/METADATA +0 -157
- collibra_connector-1.0.19.dist-info/RECORD +0 -21
- {collibra_connector-1.0.19.dist-info → collibra_connector-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {collibra_connector-1.0.19.dist-info → collibra_connector-1.1.1.dist-info}/top_level.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenTelemetry integration for Collibra Connector.
|
|
3
|
+
|
|
4
|
+
This module provides observability capabilities including:
|
|
5
|
+
- Distributed tracing for API calls
|
|
6
|
+
- Metrics for request latency and error rates
|
|
7
|
+
- Automatic instrumentation of all API operations
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from collibra_connector import CollibraConnector
|
|
11
|
+
>>> from collibra_connector.telemetry import enable_telemetry
|
|
12
|
+
>>>
|
|
13
|
+
>>> # Enable telemetry with OTLP exporter
|
|
14
|
+
>>> enable_telemetry(
|
|
15
|
+
... service_name="my-data-pipeline",
|
|
16
|
+
... otlp_endpoint="http://localhost:4317"
|
|
17
|
+
... )
|
|
18
|
+
>>>
|
|
19
|
+
>>> # All API calls are now traced
|
|
20
|
+
>>> conn = CollibraConnector(...)
|
|
21
|
+
>>> assets = conn.asset.find_assets() # Creates a span
|
|
22
|
+
|
|
23
|
+
For Grafana/Prometheus setup:
|
|
24
|
+
>>> enable_telemetry(
|
|
25
|
+
... service_name="collibra-etl",
|
|
26
|
+
... otlp_endpoint="http://otel-collector:4317",
|
|
27
|
+
... enable_metrics=True
|
|
28
|
+
... )
|
|
29
|
+
"""
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import functools
|
|
33
|
+
import logging
|
|
34
|
+
import time
|
|
35
|
+
from contextlib import contextmanager
|
|
36
|
+
from typing import Any, Callable, Dict, Optional, TypeVar, Union
|
|
37
|
+
|
|
38
|
+
# Check for OpenTelemetry availability
|
|
39
|
+
try:
|
|
40
|
+
from opentelemetry import trace, metrics
|
|
41
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
42
|
+
from opentelemetry.sdk.trace.export import (
|
|
43
|
+
BatchSpanProcessor,
|
|
44
|
+
ConsoleSpanExporter,
|
|
45
|
+
SpanExporter,
|
|
46
|
+
)
|
|
47
|
+
from opentelemetry.sdk.metrics import MeterProvider
|
|
48
|
+
from opentelemetry.sdk.metrics.export import (
|
|
49
|
+
PeriodicExportingMetricReader,
|
|
50
|
+
ConsoleMetricExporter,
|
|
51
|
+
MetricExporter,
|
|
52
|
+
)
|
|
53
|
+
from opentelemetry.sdk.resources import Resource
|
|
54
|
+
from opentelemetry.semconv.resource import ResourceAttributes
|
|
55
|
+
from opentelemetry.trace import Status, StatusCode, Span
|
|
56
|
+
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
57
|
+
|
|
58
|
+
OTEL_AVAILABLE = True
|
|
59
|
+
except ImportError:
|
|
60
|
+
OTEL_AVAILABLE = False
|
|
61
|
+
trace = None # type: ignore
|
|
62
|
+
metrics = None # type: ignore
|
|
63
|
+
|
|
64
|
+
# Check for OTLP exporter
|
|
65
|
+
try:
|
|
66
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
67
|
+
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
|
|
68
|
+
OTLP_AVAILABLE = True
|
|
69
|
+
except ImportError:
|
|
70
|
+
OTLP_AVAILABLE = False
|
|
71
|
+
OTLPSpanExporter = None # type: ignore
|
|
72
|
+
OTLPMetricExporter = None # type: ignore
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
T = TypeVar('T')
|
|
76
|
+
F = TypeVar('F', bound=Callable[..., Any])
|
|
77
|
+
|
|
78
|
+
# Module-level state
|
|
79
|
+
_tracer: Optional[Any] = None
|
|
80
|
+
_meter: Optional[Any] = None
|
|
81
|
+
_enabled: bool = False
|
|
82
|
+
|
|
83
|
+
# Metrics
|
|
84
|
+
_request_counter: Optional[Any] = None
|
|
85
|
+
_request_duration: Optional[Any] = None
|
|
86
|
+
_error_counter: Optional[Any] = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def is_telemetry_available() -> bool:
|
|
90
|
+
"""Check if OpenTelemetry is installed."""
|
|
91
|
+
return OTEL_AVAILABLE
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_telemetry_enabled() -> bool:
|
|
95
|
+
"""Check if telemetry has been enabled."""
|
|
96
|
+
return _enabled
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def enable_telemetry(
|
|
100
|
+
service_name: str = "collibra-connector",
|
|
101
|
+
service_version: str = "1.0.0",
|
|
102
|
+
otlp_endpoint: Optional[str] = None,
|
|
103
|
+
console_export: bool = False,
|
|
104
|
+
enable_metrics: bool = True,
|
|
105
|
+
custom_resource_attributes: Optional[Dict[str, str]] = None
|
|
106
|
+
) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Enable OpenTelemetry instrumentation for the Collibra Connector.
|
|
109
|
+
|
|
110
|
+
This function sets up tracing and optionally metrics collection
|
|
111
|
+
for all API calls made through the connector.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
service_name: Name of your service for tracing.
|
|
115
|
+
service_version: Version of your service.
|
|
116
|
+
otlp_endpoint: OTLP collector endpoint (e.g., "http://localhost:4317").
|
|
117
|
+
console_export: If True, also export spans to console (for debugging).
|
|
118
|
+
enable_metrics: If True, enable metrics collection.
|
|
119
|
+
custom_resource_attributes: Additional resource attributes.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if telemetry was enabled, False if OpenTelemetry is not installed.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> # For local development with console output
|
|
126
|
+
>>> enable_telemetry(service_name="my-app", console_export=True)
|
|
127
|
+
>>>
|
|
128
|
+
>>> # For production with OTLP collector
|
|
129
|
+
>>> enable_telemetry(
|
|
130
|
+
... service_name="data-pipeline",
|
|
131
|
+
... otlp_endpoint="http://otel-collector:4317"
|
|
132
|
+
... )
|
|
133
|
+
"""
|
|
134
|
+
global _tracer, _meter, _enabled
|
|
135
|
+
global _request_counter, _request_duration, _error_counter
|
|
136
|
+
|
|
137
|
+
if not OTEL_AVAILABLE:
|
|
138
|
+
logging.warning(
|
|
139
|
+
"OpenTelemetry not installed. Install with: "
|
|
140
|
+
"pip install opentelemetry-api opentelemetry-sdk"
|
|
141
|
+
)
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
# Build resource attributes
|
|
145
|
+
resource_attrs = {
|
|
146
|
+
ResourceAttributes.SERVICE_NAME: service_name,
|
|
147
|
+
ResourceAttributes.SERVICE_VERSION: service_version,
|
|
148
|
+
}
|
|
149
|
+
if custom_resource_attributes:
|
|
150
|
+
resource_attrs.update(custom_resource_attributes)
|
|
151
|
+
|
|
152
|
+
resource = Resource.create(resource_attrs)
|
|
153
|
+
|
|
154
|
+
# Set up tracing
|
|
155
|
+
tracer_provider = TracerProvider(resource=resource)
|
|
156
|
+
|
|
157
|
+
# Add exporters
|
|
158
|
+
if otlp_endpoint and OTLP_AVAILABLE:
|
|
159
|
+
otlp_exporter = OTLPSpanExporter(endpoint=otlp_endpoint, insecure=True)
|
|
160
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
|
|
161
|
+
elif otlp_endpoint and not OTLP_AVAILABLE:
|
|
162
|
+
logging.warning(
|
|
163
|
+
"OTLP endpoint specified but exporter not installed. "
|
|
164
|
+
"Install with: pip install opentelemetry-exporter-otlp"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if console_export:
|
|
168
|
+
tracer_provider.add_span_processor(
|
|
169
|
+
BatchSpanProcessor(ConsoleSpanExporter())
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
trace.set_tracer_provider(tracer_provider)
|
|
173
|
+
_tracer = trace.get_tracer("collibra-connector", service_version)
|
|
174
|
+
|
|
175
|
+
# Set up metrics
|
|
176
|
+
if enable_metrics:
|
|
177
|
+
metric_readers = []
|
|
178
|
+
|
|
179
|
+
if otlp_endpoint and OTLP_AVAILABLE:
|
|
180
|
+
otlp_metric_exporter = OTLPMetricExporter(
|
|
181
|
+
endpoint=otlp_endpoint,
|
|
182
|
+
insecure=True
|
|
183
|
+
)
|
|
184
|
+
metric_readers.append(
|
|
185
|
+
PeriodicExportingMetricReader(otlp_metric_exporter)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if console_export:
|
|
189
|
+
metric_readers.append(
|
|
190
|
+
PeriodicExportingMetricReader(ConsoleMetricExporter())
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if metric_readers:
|
|
194
|
+
meter_provider = MeterProvider(
|
|
195
|
+
resource=resource,
|
|
196
|
+
metric_readers=metric_readers
|
|
197
|
+
)
|
|
198
|
+
metrics.set_meter_provider(meter_provider)
|
|
199
|
+
_meter = metrics.get_meter("collibra-connector", service_version)
|
|
200
|
+
|
|
201
|
+
# Create metrics
|
|
202
|
+
_request_counter = _meter.create_counter(
|
|
203
|
+
name="collibra_requests_total",
|
|
204
|
+
description="Total number of Collibra API requests",
|
|
205
|
+
unit="1"
|
|
206
|
+
)
|
|
207
|
+
_request_duration = _meter.create_histogram(
|
|
208
|
+
name="collibra_request_duration_seconds",
|
|
209
|
+
description="Duration of Collibra API requests",
|
|
210
|
+
unit="s"
|
|
211
|
+
)
|
|
212
|
+
_error_counter = _meter.create_counter(
|
|
213
|
+
name="collibra_errors_total",
|
|
214
|
+
description="Total number of Collibra API errors",
|
|
215
|
+
unit="1"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
_enabled = True
|
|
219
|
+
logging.info(f"Telemetry enabled for service: {service_name}")
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def disable_telemetry() -> None:
|
|
224
|
+
"""Disable telemetry and clean up resources."""
|
|
225
|
+
global _tracer, _meter, _enabled
|
|
226
|
+
global _request_counter, _request_duration, _error_counter
|
|
227
|
+
|
|
228
|
+
_tracer = None
|
|
229
|
+
_meter = None
|
|
230
|
+
_request_counter = None
|
|
231
|
+
_request_duration = None
|
|
232
|
+
_error_counter = None
|
|
233
|
+
_enabled = False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@contextmanager
|
|
237
|
+
def span(
|
|
238
|
+
name: str,
|
|
239
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
240
|
+
record_exception: bool = True
|
|
241
|
+
):
|
|
242
|
+
"""
|
|
243
|
+
Context manager for creating a traced span.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
name: Name of the span.
|
|
247
|
+
attributes: Optional attributes to add to the span.
|
|
248
|
+
record_exception: If True, record exceptions that occur.
|
|
249
|
+
|
|
250
|
+
Yields:
|
|
251
|
+
The span object (or None if telemetry not enabled).
|
|
252
|
+
|
|
253
|
+
Example:
|
|
254
|
+
>>> with span("process_assets", {"asset_count": 100}) as s:
|
|
255
|
+
... # Do work
|
|
256
|
+
... s.set_attribute("processed", 100)
|
|
257
|
+
"""
|
|
258
|
+
if not _enabled or not _tracer:
|
|
259
|
+
yield None
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
with _tracer.start_as_current_span(name) as s:
|
|
263
|
+
if attributes:
|
|
264
|
+
for key, value in attributes.items():
|
|
265
|
+
s.set_attribute(key, value)
|
|
266
|
+
try:
|
|
267
|
+
yield s
|
|
268
|
+
except Exception as e:
|
|
269
|
+
if record_exception:
|
|
270
|
+
s.record_exception(e)
|
|
271
|
+
s.set_status(Status(StatusCode.ERROR, str(e)))
|
|
272
|
+
raise
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def traced(
|
|
276
|
+
span_name: Optional[str] = None,
|
|
277
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
278
|
+
) -> Callable[[F], F]:
|
|
279
|
+
"""
|
|
280
|
+
Decorator for tracing function calls.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
span_name: Custom span name (defaults to function name).
|
|
284
|
+
attributes: Static attributes to add to every span.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Decorated function.
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
>>> @traced("fetch_customer_data")
|
|
291
|
+
... def get_customers(limit: int):
|
|
292
|
+
... return connector.asset.find_assets(limit=limit)
|
|
293
|
+
"""
|
|
294
|
+
def decorator(func: F) -> F:
|
|
295
|
+
@functools.wraps(func)
|
|
296
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
297
|
+
if not _enabled or not _tracer:
|
|
298
|
+
return func(*args, **kwargs)
|
|
299
|
+
|
|
300
|
+
name = span_name or func.__qualname__
|
|
301
|
+
span_attrs = dict(attributes) if attributes else {}
|
|
302
|
+
|
|
303
|
+
# Add function args as attributes (be careful with sensitive data)
|
|
304
|
+
span_attrs["function.name"] = func.__name__
|
|
305
|
+
span_attrs["function.module"] = func.__module__
|
|
306
|
+
|
|
307
|
+
start_time = time.time()
|
|
308
|
+
with _tracer.start_as_current_span(name, attributes=span_attrs) as s:
|
|
309
|
+
try:
|
|
310
|
+
result = func(*args, **kwargs)
|
|
311
|
+
|
|
312
|
+
# Record duration
|
|
313
|
+
duration = time.time() - start_time
|
|
314
|
+
s.set_attribute("duration_seconds", duration)
|
|
315
|
+
|
|
316
|
+
# Record metrics
|
|
317
|
+
if _request_counter:
|
|
318
|
+
_request_counter.add(
|
|
319
|
+
1,
|
|
320
|
+
{"operation": name, "status": "success"}
|
|
321
|
+
)
|
|
322
|
+
if _request_duration:
|
|
323
|
+
_request_duration.record(
|
|
324
|
+
duration,
|
|
325
|
+
{"operation": name}
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return result
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
s.record_exception(e)
|
|
332
|
+
s.set_status(Status(StatusCode.ERROR, str(e)))
|
|
333
|
+
|
|
334
|
+
# Record error metric
|
|
335
|
+
if _error_counter:
|
|
336
|
+
_error_counter.add(
|
|
337
|
+
1,
|
|
338
|
+
{"operation": name, "error_type": type(e).__name__}
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
raise
|
|
342
|
+
|
|
343
|
+
return wrapper # type: ignore
|
|
344
|
+
return decorator
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def traced_async(
|
|
348
|
+
span_name: Optional[str] = None,
|
|
349
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
350
|
+
) -> Callable[[F], F]:
|
|
351
|
+
"""
|
|
352
|
+
Decorator for tracing async function calls.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
span_name: Custom span name (defaults to function name).
|
|
356
|
+
attributes: Static attributes to add to every span.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Decorated async function.
|
|
360
|
+
|
|
361
|
+
Example:
|
|
362
|
+
>>> @traced_async("async_fetch_data")
|
|
363
|
+
... async def fetch_data():
|
|
364
|
+
... return await connector.asset.get_assets_batch(ids)
|
|
365
|
+
"""
|
|
366
|
+
def decorator(func: F) -> F:
|
|
367
|
+
@functools.wraps(func)
|
|
368
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
369
|
+
if not _enabled or not _tracer:
|
|
370
|
+
return await func(*args, **kwargs)
|
|
371
|
+
|
|
372
|
+
name = span_name or func.__qualname__
|
|
373
|
+
span_attrs = dict(attributes) if attributes else {}
|
|
374
|
+
span_attrs["function.name"] = func.__name__
|
|
375
|
+
span_attrs["async"] = True
|
|
376
|
+
|
|
377
|
+
start_time = time.time()
|
|
378
|
+
with _tracer.start_as_current_span(name, attributes=span_attrs) as s:
|
|
379
|
+
try:
|
|
380
|
+
result = await func(*args, **kwargs)
|
|
381
|
+
|
|
382
|
+
duration = time.time() - start_time
|
|
383
|
+
s.set_attribute("duration_seconds", duration)
|
|
384
|
+
|
|
385
|
+
if _request_counter:
|
|
386
|
+
_request_counter.add(
|
|
387
|
+
1,
|
|
388
|
+
{"operation": name, "status": "success"}
|
|
389
|
+
)
|
|
390
|
+
if _request_duration:
|
|
391
|
+
_request_duration.record(duration, {"operation": name})
|
|
392
|
+
|
|
393
|
+
return result
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
s.record_exception(e)
|
|
397
|
+
s.set_status(Status(StatusCode.ERROR, str(e)))
|
|
398
|
+
|
|
399
|
+
if _error_counter:
|
|
400
|
+
_error_counter.add(
|
|
401
|
+
1,
|
|
402
|
+
{"operation": name, "error_type": type(e).__name__}
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
raise
|
|
406
|
+
|
|
407
|
+
return wrapper # type: ignore
|
|
408
|
+
return decorator
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class TracedCollibraConnector:
|
|
412
|
+
"""
|
|
413
|
+
Wrapper that adds tracing to all CollibraConnector operations.
|
|
414
|
+
|
|
415
|
+
This class wraps an existing connector and automatically
|
|
416
|
+
traces all API calls.
|
|
417
|
+
|
|
418
|
+
Example:
|
|
419
|
+
>>> from collibra_connector import CollibraConnector
|
|
420
|
+
>>> from collibra_connector.telemetry import TracedCollibraConnector, enable_telemetry
|
|
421
|
+
>>>
|
|
422
|
+
>>> enable_telemetry(service_name="my-app", otlp_endpoint="localhost:4317")
|
|
423
|
+
>>>
|
|
424
|
+
>>> base_conn = CollibraConnector(...)
|
|
425
|
+
>>> conn = TracedCollibraConnector(base_conn)
|
|
426
|
+
>>>
|
|
427
|
+
>>> # All operations are now traced
|
|
428
|
+
>>> assets = conn.asset.find_assets()
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
def __init__(self, connector: Any) -> None:
|
|
432
|
+
"""
|
|
433
|
+
Initialize traced connector.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
connector: The CollibraConnector instance to wrap.
|
|
437
|
+
"""
|
|
438
|
+
self._connector = connector
|
|
439
|
+
self._wrapped_apis: Dict[str, Any] = {}
|
|
440
|
+
|
|
441
|
+
def __getattr__(self, name: str) -> Any:
|
|
442
|
+
"""Get wrapped API module with tracing."""
|
|
443
|
+
if name.startswith('_'):
|
|
444
|
+
return getattr(self._connector, name)
|
|
445
|
+
|
|
446
|
+
if name in self._wrapped_apis:
|
|
447
|
+
return self._wrapped_apis[name]
|
|
448
|
+
|
|
449
|
+
original = getattr(self._connector, name)
|
|
450
|
+
wrapped = TracedAPI(original, name)
|
|
451
|
+
self._wrapped_apis[name] = wrapped
|
|
452
|
+
return wrapped
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class TracedAPI:
|
|
456
|
+
"""Wrapper that traces all method calls on an API module."""
|
|
457
|
+
|
|
458
|
+
def __init__(self, api: Any, api_name: str) -> None:
|
|
459
|
+
self._api = api
|
|
460
|
+
self._api_name = api_name
|
|
461
|
+
|
|
462
|
+
def __getattr__(self, name: str) -> Any:
|
|
463
|
+
original = getattr(self._api, name)
|
|
464
|
+
|
|
465
|
+
if not callable(original):
|
|
466
|
+
return original
|
|
467
|
+
|
|
468
|
+
@functools.wraps(original)
|
|
469
|
+
def traced_method(*args: Any, **kwargs: Any) -> Any:
|
|
470
|
+
span_name = f"collibra.{self._api_name}.{name}"
|
|
471
|
+
attributes = {
|
|
472
|
+
"collibra.api": self._api_name,
|
|
473
|
+
"collibra.method": name,
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
# Add some kwargs as attributes (filter sensitive ones)
|
|
477
|
+
safe_kwargs = {
|
|
478
|
+
k: str(v)[:100] for k, v in kwargs.items()
|
|
479
|
+
if k not in ('password', 'token', 'secret', 'auth')
|
|
480
|
+
}
|
|
481
|
+
if safe_kwargs:
|
|
482
|
+
attributes["collibra.params"] = str(safe_kwargs)
|
|
483
|
+
|
|
484
|
+
start_time = time.time()
|
|
485
|
+
|
|
486
|
+
if not _enabled or not _tracer:
|
|
487
|
+
return original(*args, **kwargs)
|
|
488
|
+
|
|
489
|
+
with _tracer.start_as_current_span(span_name, attributes=attributes) as s:
|
|
490
|
+
try:
|
|
491
|
+
result = original(*args, **kwargs)
|
|
492
|
+
|
|
493
|
+
duration = time.time() - start_time
|
|
494
|
+
s.set_attribute("duration_seconds", duration)
|
|
495
|
+
|
|
496
|
+
# Add result info
|
|
497
|
+
if isinstance(result, dict):
|
|
498
|
+
if "total" in result:
|
|
499
|
+
s.set_attribute("result.total", result["total"])
|
|
500
|
+
if "results" in result:
|
|
501
|
+
s.set_attribute("result.count", len(result["results"]))
|
|
502
|
+
|
|
503
|
+
if _request_counter:
|
|
504
|
+
_request_counter.add(
|
|
505
|
+
1,
|
|
506
|
+
{"api": self._api_name, "method": name, "status": "success"}
|
|
507
|
+
)
|
|
508
|
+
if _request_duration:
|
|
509
|
+
_request_duration.record(
|
|
510
|
+
duration,
|
|
511
|
+
{"api": self._api_name, "method": name}
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
return result
|
|
515
|
+
|
|
516
|
+
except Exception as e:
|
|
517
|
+
s.record_exception(e)
|
|
518
|
+
s.set_status(Status(StatusCode.ERROR, str(e)))
|
|
519
|
+
|
|
520
|
+
if _error_counter:
|
|
521
|
+
_error_counter.add(
|
|
522
|
+
1,
|
|
523
|
+
{
|
|
524
|
+
"api": self._api_name,
|
|
525
|
+
"method": name,
|
|
526
|
+
"error_type": type(e).__name__
|
|
527
|
+
}
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
raise
|
|
531
|
+
|
|
532
|
+
return traced_method
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def get_current_trace_id() -> Optional[str]:
|
|
536
|
+
"""Get the current trace ID if in a traced context."""
|
|
537
|
+
if not _enabled or not trace:
|
|
538
|
+
return None
|
|
539
|
+
|
|
540
|
+
current_span = trace.get_current_span()
|
|
541
|
+
if current_span:
|
|
542
|
+
return format(current_span.get_span_context().trace_id, '032x')
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def get_current_span_id() -> Optional[str]:
|
|
547
|
+
"""Get the current span ID if in a traced context."""
|
|
548
|
+
if not _enabled or not trace:
|
|
549
|
+
return None
|
|
550
|
+
|
|
551
|
+
current_span = trace.get_current_span()
|
|
552
|
+
if current_span:
|
|
553
|
+
return format(current_span.get_span_context().span_id, '016x')
|
|
554
|
+
return None
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def add_span_attributes(attributes: Dict[str, Any]) -> None:
|
|
558
|
+
"""Add attributes to the current span."""
|
|
559
|
+
if not _enabled or not trace:
|
|
560
|
+
return
|
|
561
|
+
|
|
562
|
+
current_span = trace.get_current_span()
|
|
563
|
+
if current_span:
|
|
564
|
+
for key, value in attributes.items():
|
|
565
|
+
current_span.set_attribute(key, value)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def record_exception(exception: Exception) -> None:
|
|
569
|
+
"""Record an exception on the current span."""
|
|
570
|
+
if not _enabled or not trace:
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
current_span = trace.get_current_span()
|
|
574
|
+
if current_span:
|
|
575
|
+
current_span.record_exception(exception)
|
|
576
|
+
current_span.set_status(Status(StatusCode.ERROR, str(exception)))
|