robotframework-tracer 0.1.0__py3-none-any.whl → 0.2.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.
- robotframework_tracer/attributes.py +22 -20
- robotframework_tracer/config.py +1 -3
- robotframework_tracer/listener.py +118 -37
- robotframework_tracer/span_builder.py +16 -14
- robotframework_tracer/version.py +1 -1
- {robotframework_tracer-0.1.0.dist-info → robotframework_tracer-0.2.1.dist-info}/METADATA +35 -7
- robotframework_tracer-0.2.1.dist-info/RECORD +10 -0
- robotframework_tracer-0.1.0.dist-info/RECORD +0 -10
- {robotframework_tracer-0.1.0.dist-info → robotframework_tracer-0.2.1.dist-info}/WHEEL +0 -0
- {robotframework_tracer-0.1.0.dist-info → robotframework_tracer-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -43,18 +43,18 @@ class AttributeExtractor:
|
|
|
43
43
|
}
|
|
44
44
|
if data.source:
|
|
45
45
|
attrs[RFAttributes.SUITE_SOURCE] = str(data.source)
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
# Extract suite metadata
|
|
48
|
-
if hasattr(data,
|
|
48
|
+
if hasattr(data, "metadata") and data.metadata:
|
|
49
49
|
for key, value in data.metadata.items():
|
|
50
50
|
attrs[f"{RFAttributes.SUITE_METADATA}.{key}"] = str(value)
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
# Add timing information
|
|
53
|
-
if hasattr(result,
|
|
53
|
+
if hasattr(result, "starttime") and result.starttime:
|
|
54
54
|
attrs[RFAttributes.START_TIME] = result.starttime
|
|
55
|
-
if hasattr(result,
|
|
55
|
+
if hasattr(result, "endtime") and result.endtime:
|
|
56
56
|
attrs[RFAttributes.END_TIME] = result.endtime
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
return attrs
|
|
59
59
|
|
|
60
60
|
@staticmethod
|
|
@@ -66,25 +66,25 @@ class AttributeExtractor:
|
|
|
66
66
|
}
|
|
67
67
|
if data.tags:
|
|
68
68
|
attrs[RFAttributes.TEST_TAGS] = list(data.tags)
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
# Add test template if available
|
|
71
|
-
if hasattr(data,
|
|
71
|
+
if hasattr(data, "template") and data.template:
|
|
72
72
|
attrs[RFAttributes.TEST_TEMPLATE] = str(data.template)
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
# Add test timeout if available
|
|
75
|
-
if hasattr(data,
|
|
75
|
+
if hasattr(data, "timeout") and data.timeout:
|
|
76
76
|
attrs[RFAttributes.TEST_TIMEOUT] = str(data.timeout)
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
# Add timing information
|
|
79
|
-
if hasattr(result,
|
|
79
|
+
if hasattr(result, "starttime") and result.starttime:
|
|
80
80
|
attrs[RFAttributes.START_TIME] = result.starttime
|
|
81
|
-
if hasattr(result,
|
|
81
|
+
if hasattr(result, "endtime") and result.endtime:
|
|
82
82
|
attrs[RFAttributes.END_TIME] = result.endtime
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
# Add message if available
|
|
85
|
-
if hasattr(result,
|
|
85
|
+
if hasattr(result, "message") and result.message:
|
|
86
86
|
attrs[RFAttributes.MESSAGE] = result.message
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
return attrs
|
|
89
89
|
|
|
90
90
|
@staticmethod
|
|
@@ -95,11 +95,13 @@ class AttributeExtractor:
|
|
|
95
95
|
RFAttributes.KEYWORD_TYPE: data.type,
|
|
96
96
|
}
|
|
97
97
|
# Try to get library name (may not always be available)
|
|
98
|
-
if hasattr(data,
|
|
98
|
+
if hasattr(data, "libname") and data.libname:
|
|
99
99
|
attrs[RFAttributes.KEYWORD_LIBRARY] = data.libname
|
|
100
|
-
elif hasattr(data,
|
|
101
|
-
attrs[RFAttributes.KEYWORD_LIBRARY] =
|
|
102
|
-
|
|
100
|
+
elif hasattr(data, "owner") and data.owner:
|
|
101
|
+
attrs[RFAttributes.KEYWORD_LIBRARY] = (
|
|
102
|
+
data.owner.name if hasattr(data.owner, "name") else str(data.owner)
|
|
103
|
+
)
|
|
104
|
+
|
|
103
105
|
if data.args:
|
|
104
106
|
args_str = ", ".join(str(arg)[:max_arg_length] for arg in data.args[:10])
|
|
105
107
|
if len(args_str) > max_arg_length:
|
robotframework_tracer/config.py
CHANGED
|
@@ -8,9 +8,7 @@ class TracerConfig:
|
|
|
8
8
|
self.endpoint = self._get_config(
|
|
9
9
|
"endpoint", kwargs, "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318/v1/traces"
|
|
10
10
|
)
|
|
11
|
-
self.service_name = self._get_config(
|
|
12
|
-
"service_name", kwargs, "OTEL_SERVICE_NAME", "rf"
|
|
13
|
-
)
|
|
11
|
+
self.service_name = self._get_config("service_name", kwargs, "OTEL_SERVICE_NAME", "rf")
|
|
14
12
|
self.protocol = self._get_config("protocol", kwargs, "RF_TRACER_PROTOCOL", "http")
|
|
15
13
|
self.capture_arguments = self._get_bool_config(
|
|
16
14
|
"capture_arguments", kwargs, "RF_TRACER_CAPTURE_ARGUMENTS", True
|
|
@@ -5,6 +5,7 @@ from opentelemetry.sdk.trace import TracerProvider
|
|
|
5
5
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
6
6
|
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased, ParentBased
|
|
7
7
|
from opentelemetry.semconv.resource import ResourceAttributes
|
|
8
|
+
from opentelemetry.propagate import inject
|
|
8
9
|
import platform
|
|
9
10
|
import sys
|
|
10
11
|
import robot
|
|
@@ -12,9 +13,20 @@ import robot
|
|
|
12
13
|
from .config import TracerConfig
|
|
13
14
|
from .span_builder import SpanBuilder
|
|
14
15
|
|
|
16
|
+
# Try to import Robot Framework BuiltIn library for variable setting
|
|
17
|
+
try:
|
|
18
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
19
|
+
|
|
20
|
+
BUILTIN_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
BUILTIN_AVAILABLE = False
|
|
23
|
+
|
|
15
24
|
# Try to import gRPC exporter (optional dependency)
|
|
16
25
|
try:
|
|
17
|
-
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import
|
|
26
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
|
|
27
|
+
OTLPSpanExporter as GRPCExporter,
|
|
28
|
+
)
|
|
29
|
+
|
|
18
30
|
GRPC_AVAILABLE = True
|
|
19
31
|
except ImportError:
|
|
20
32
|
GRPC_AVAILABLE = False
|
|
@@ -25,9 +37,19 @@ class TracingListener:
|
|
|
25
37
|
|
|
26
38
|
ROBOT_LISTENER_API_VERSION = 3
|
|
27
39
|
|
|
28
|
-
def __init__(
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
endpoint=None,
|
|
43
|
+
service_name=None,
|
|
44
|
+
protocol=None,
|
|
45
|
+
capture_arguments=None,
|
|
46
|
+
max_arg_length=None,
|
|
47
|
+
capture_logs=None,
|
|
48
|
+
sample_rate=None,
|
|
49
|
+
span_prefix_style=None,
|
|
50
|
+
log_level=None,
|
|
51
|
+
max_log_length=None,
|
|
52
|
+
):
|
|
31
53
|
"""Initialize the tracing listener.
|
|
32
54
|
|
|
33
55
|
Args:
|
|
@@ -45,26 +67,26 @@ class TracingListener:
|
|
|
45
67
|
# Build kwargs dict from provided arguments
|
|
46
68
|
kwargs = {}
|
|
47
69
|
if endpoint is not None:
|
|
48
|
-
kwargs[
|
|
70
|
+
kwargs["endpoint"] = endpoint
|
|
49
71
|
if service_name is not None:
|
|
50
|
-
kwargs[
|
|
72
|
+
kwargs["service_name"] = service_name
|
|
51
73
|
if protocol is not None:
|
|
52
|
-
kwargs[
|
|
74
|
+
kwargs["protocol"] = protocol
|
|
53
75
|
if capture_arguments is not None:
|
|
54
|
-
kwargs[
|
|
76
|
+
kwargs["capture_arguments"] = capture_arguments
|
|
55
77
|
if max_arg_length is not None:
|
|
56
|
-
kwargs[
|
|
78
|
+
kwargs["max_arg_length"] = max_arg_length
|
|
57
79
|
if capture_logs is not None:
|
|
58
|
-
kwargs[
|
|
80
|
+
kwargs["capture_logs"] = capture_logs
|
|
59
81
|
if sample_rate is not None:
|
|
60
|
-
kwargs[
|
|
82
|
+
kwargs["sample_rate"] = sample_rate
|
|
61
83
|
if span_prefix_style is not None:
|
|
62
|
-
kwargs[
|
|
84
|
+
kwargs["span_prefix_style"] = span_prefix_style
|
|
63
85
|
if log_level is not None:
|
|
64
|
-
kwargs[
|
|
86
|
+
kwargs["log_level"] = log_level
|
|
65
87
|
if max_log_length is not None:
|
|
66
|
-
kwargs[
|
|
67
|
-
|
|
88
|
+
kwargs["max_log_length"] = max_log_length
|
|
89
|
+
|
|
68
90
|
self.config = TracerConfig(**kwargs)
|
|
69
91
|
|
|
70
92
|
# Initialize OpenTelemetry with automatic resource detection
|
|
@@ -80,25 +102,27 @@ class TracingListener:
|
|
|
80
102
|
ResourceAttributes.OS_VERSION: platform.release(),
|
|
81
103
|
}
|
|
82
104
|
resource = Resource.create(resource_attrs)
|
|
83
|
-
|
|
105
|
+
|
|
84
106
|
# Configure sampling only if sample_rate < 1.0
|
|
85
107
|
if self.config.sample_rate < 1.0:
|
|
86
108
|
sampler = ParentBased(root=TraceIdRatioBased(self.config.sample_rate))
|
|
87
109
|
provider = TracerProvider(resource=resource, sampler=sampler)
|
|
88
110
|
else:
|
|
89
111
|
provider = TracerProvider(resource=resource)
|
|
90
|
-
|
|
112
|
+
|
|
91
113
|
# Select exporter based on protocol
|
|
92
114
|
if self.config.protocol == "grpc":
|
|
93
115
|
if not GRPC_AVAILABLE:
|
|
94
|
-
print(
|
|
116
|
+
print(
|
|
117
|
+
"Warning: gRPC exporter not available. Install with: pip install opentelemetry-exporter-otlp-proto-grpc"
|
|
118
|
+
)
|
|
95
119
|
print("Falling back to HTTP exporter")
|
|
96
120
|
exporter = HTTPExporter(endpoint=self.config.endpoint)
|
|
97
121
|
else:
|
|
98
122
|
exporter = GRPCExporter(endpoint=self.config.endpoint)
|
|
99
123
|
else:
|
|
100
124
|
exporter = HTTPExporter(endpoint=self.config.endpoint)
|
|
101
|
-
|
|
125
|
+
|
|
102
126
|
processor = BatchSpanProcessor(exporter)
|
|
103
127
|
provider.add_span_processor(processor)
|
|
104
128
|
trace.set_tracer_provider(provider)
|
|
@@ -106,10 +130,56 @@ class TracingListener:
|
|
|
106
130
|
self.tracer = trace.get_tracer(__name__)
|
|
107
131
|
self.span_stack = []
|
|
108
132
|
|
|
133
|
+
def _set_trace_context_variables(self):
|
|
134
|
+
"""Set Robot Framework variables with current trace context."""
|
|
135
|
+
if not BUILTIN_AVAILABLE:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
# Get current trace context
|
|
140
|
+
headers = {}
|
|
141
|
+
inject(headers) # Injects traceparent, tracestate headers
|
|
142
|
+
|
|
143
|
+
# Get current span info
|
|
144
|
+
current_span = trace.get_current_span()
|
|
145
|
+
trace_id = None
|
|
146
|
+
span_id = None
|
|
147
|
+
|
|
148
|
+
if current_span.is_recording():
|
|
149
|
+
span_context = current_span.get_span_context()
|
|
150
|
+
trace_id = format(span_context.trace_id, "032x")
|
|
151
|
+
span_id = format(span_context.span_id, "016x")
|
|
152
|
+
|
|
153
|
+
# Set RF variables for different protocols
|
|
154
|
+
builtin = BuiltIn()
|
|
155
|
+
|
|
156
|
+
# HTTP headers (for REST APIs, web services)
|
|
157
|
+
builtin.set_test_variable("${TRACE_HEADERS}", headers)
|
|
158
|
+
|
|
159
|
+
# Individual trace components (for custom protocols like Diameter)
|
|
160
|
+
if trace_id:
|
|
161
|
+
builtin.set_test_variable("${TRACE_ID}", trace_id)
|
|
162
|
+
if span_id:
|
|
163
|
+
builtin.set_test_variable("${SPAN_ID}", span_id)
|
|
164
|
+
|
|
165
|
+
# W3C traceparent format (for manual header construction)
|
|
166
|
+
if headers.get("traceparent"):
|
|
167
|
+
builtin.set_test_variable("${TRACEPARENT}", headers["traceparent"])
|
|
168
|
+
|
|
169
|
+
# Tracestate (for vendor-specific data)
|
|
170
|
+
if headers.get("tracestate"):
|
|
171
|
+
builtin.set_test_variable("${TRACESTATE}", headers["tracestate"])
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
# Silently ignore errors to avoid breaking tests
|
|
175
|
+
pass
|
|
176
|
+
|
|
109
177
|
def start_suite(self, data, result):
|
|
110
178
|
"""Create root span for suite."""
|
|
111
179
|
try:
|
|
112
|
-
span = SpanBuilder.create_suite_span(
|
|
180
|
+
span = SpanBuilder.create_suite_span(
|
|
181
|
+
self.tracer, data, result, self.config.span_prefix_style
|
|
182
|
+
)
|
|
113
183
|
self.span_stack.append(span)
|
|
114
184
|
except Exception as e:
|
|
115
185
|
print(f"TracingListener error in start_suite: {e}")
|
|
@@ -130,8 +200,15 @@ class TracingListener:
|
|
|
130
200
|
parent_context = (
|
|
131
201
|
trace.set_span_in_context(self.span_stack[-1]) if self.span_stack else None
|
|
132
202
|
)
|
|
133
|
-
span = SpanBuilder.create_test_span(
|
|
203
|
+
span = SpanBuilder.create_test_span(
|
|
204
|
+
self.tracer, data, result, parent_context, self.config.span_prefix_style
|
|
205
|
+
)
|
|
134
206
|
self.span_stack.append(span)
|
|
207
|
+
|
|
208
|
+
# Set trace context variables within the span context
|
|
209
|
+
with trace.use_span(span):
|
|
210
|
+
self._set_trace_context_variables()
|
|
211
|
+
|
|
135
212
|
except Exception as e:
|
|
136
213
|
print(f"TracingListener error in start_test: {e}")
|
|
137
214
|
|
|
@@ -157,10 +234,15 @@ class TracingListener:
|
|
|
157
234
|
trace.set_span_in_context(self.span_stack[-1]) if self.span_stack else None
|
|
158
235
|
)
|
|
159
236
|
span = SpanBuilder.create_keyword_span(
|
|
160
|
-
self.tracer,
|
|
237
|
+
self.tracer,
|
|
238
|
+
data,
|
|
239
|
+
result,
|
|
240
|
+
parent_context,
|
|
241
|
+
self.config.max_arg_length,
|
|
242
|
+
self.config.span_prefix_style,
|
|
161
243
|
)
|
|
162
244
|
self.span_stack.append(span)
|
|
163
|
-
|
|
245
|
+
|
|
164
246
|
# Add event for setup/teardown start
|
|
165
247
|
if data.type in ("SETUP", "TEARDOWN"):
|
|
166
248
|
span.add_event(f"{data.type.lower()}.start", {"keyword": data.name})
|
|
@@ -172,14 +254,13 @@ class TracingListener:
|
|
|
172
254
|
try:
|
|
173
255
|
if self.span_stack:
|
|
174
256
|
span = self.span_stack.pop()
|
|
175
|
-
|
|
257
|
+
|
|
176
258
|
# Add event for setup/teardown end
|
|
177
259
|
if data.type in ("SETUP", "TEARDOWN"):
|
|
178
|
-
span.add_event(
|
|
179
|
-
"keyword": data.name,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
260
|
+
span.add_event(
|
|
261
|
+
f"{data.type.lower()}.end", {"keyword": data.name, "status": result.status}
|
|
262
|
+
)
|
|
263
|
+
|
|
183
264
|
SpanBuilder.set_span_status(span, result)
|
|
184
265
|
if result.status == "FAIL":
|
|
185
266
|
SpanBuilder.add_error_event(span, result)
|
|
@@ -202,31 +283,31 @@ class TracingListener:
|
|
|
202
283
|
try:
|
|
203
284
|
if not self.config.capture_logs or not self.span_stack:
|
|
204
285
|
return
|
|
205
|
-
|
|
286
|
+
|
|
206
287
|
# Filter by log level
|
|
207
288
|
log_levels = {"TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, "ERROR": 4, "FAIL": 5}
|
|
208
289
|
min_level = log_levels.get(self.config.log_level, 2)
|
|
209
290
|
msg_level = log_levels.get(message.level, 2)
|
|
210
|
-
|
|
291
|
+
|
|
211
292
|
if msg_level < min_level:
|
|
212
293
|
return
|
|
213
|
-
|
|
294
|
+
|
|
214
295
|
# Get current span
|
|
215
296
|
current_span = self.span_stack[-1]
|
|
216
|
-
|
|
297
|
+
|
|
217
298
|
# Limit message length
|
|
218
299
|
log_text = message.message
|
|
219
300
|
if len(log_text) > self.config.max_log_length:
|
|
220
|
-
log_text = log_text[:self.config.max_log_length] + "..."
|
|
221
|
-
|
|
301
|
+
log_text = log_text[: self.config.max_log_length] + "..."
|
|
302
|
+
|
|
222
303
|
# Add log as span event (convert timestamp to string)
|
|
223
304
|
event_attrs = {
|
|
224
305
|
"message": log_text,
|
|
225
306
|
"level": message.level,
|
|
226
307
|
}
|
|
227
|
-
if hasattr(message,
|
|
308
|
+
if hasattr(message, "timestamp") and message.timestamp:
|
|
228
309
|
event_attrs["timestamp"] = str(message.timestamp)
|
|
229
|
-
|
|
310
|
+
|
|
230
311
|
current_span.add_event(f"log.{message.level.lower()}", event_attrs)
|
|
231
312
|
except RecursionError:
|
|
232
313
|
# Avoid infinite recursion if logging causes more logs
|
|
@@ -43,16 +43,16 @@ class SpanBuilder:
|
|
|
43
43
|
"""Create root span for test suite."""
|
|
44
44
|
attrs = AttributeExtractor.from_suite(data, result)
|
|
45
45
|
name = SpanBuilder._add_prefix(data.name, "SUITE", prefix_style)
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
# Create context with baggage
|
|
48
48
|
ctx = baggage.set_baggage("rf.suite.id", result.id)
|
|
49
49
|
ctx = baggage.set_baggage("rf.version", robot.version.get_version(), ctx)
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
# Add suite metadata to baggage (limit to avoid too much data)
|
|
52
|
-
if hasattr(data,
|
|
52
|
+
if hasattr(data, "metadata") and data.metadata:
|
|
53
53
|
for key, value in list(data.metadata.items())[:5]: # Limit to 5 metadata items
|
|
54
54
|
ctx = baggage.set_baggage(f"rf.suite.metadata.{key}", str(value), ctx)
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
span = tracer.start_span(name, context=ctx, kind=trace.SpanKind.INTERNAL, attributes=attrs)
|
|
57
57
|
return span
|
|
58
58
|
|
|
@@ -67,10 +67,12 @@ class SpanBuilder:
|
|
|
67
67
|
return span
|
|
68
68
|
|
|
69
69
|
@staticmethod
|
|
70
|
-
def create_keyword_span(
|
|
70
|
+
def create_keyword_span(
|
|
71
|
+
tracer, data, result, parent_context, max_arg_length=200, prefix_style="none"
|
|
72
|
+
):
|
|
71
73
|
"""Create child span for keyword."""
|
|
72
74
|
attrs = AttributeExtractor.from_keyword(data, result, max_arg_length)
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
# Build keyword name with arguments (like RF test step line)
|
|
75
77
|
kw_name = data.name
|
|
76
78
|
if data.args:
|
|
@@ -80,16 +82,16 @@ class SpanBuilder:
|
|
|
80
82
|
if len(args_str) > 100:
|
|
81
83
|
args_str = args_str[:100] + "..."
|
|
82
84
|
kw_name = f"{data.name} {args_str}"
|
|
83
|
-
|
|
85
|
+
|
|
84
86
|
# Determine span type for prefix
|
|
85
87
|
if data.type in ("SETUP", "TEARDOWN"):
|
|
86
88
|
span_type = data.type
|
|
87
89
|
else:
|
|
88
90
|
span_type = "KEYWORD"
|
|
89
|
-
|
|
91
|
+
|
|
90
92
|
# Add prefix based on style
|
|
91
93
|
kw_name = SpanBuilder._add_prefix(kw_name, span_type, prefix_style)
|
|
92
|
-
|
|
94
|
+
|
|
93
95
|
span = tracer.start_span(
|
|
94
96
|
kw_name, context=parent_context, kind=trace.SpanKind.INTERNAL, attributes=attrs
|
|
95
97
|
)
|
|
@@ -115,14 +117,14 @@ class SpanBuilder:
|
|
|
115
117
|
"message": result.message,
|
|
116
118
|
"rf.status": "FAIL",
|
|
117
119
|
}
|
|
118
|
-
|
|
120
|
+
|
|
119
121
|
# Try to extract exception type if available
|
|
120
|
-
if hasattr(result,
|
|
122
|
+
if hasattr(result, "error") and result.error:
|
|
121
123
|
event_attrs["exception.type"] = type(result.error).__name__
|
|
122
124
|
event_attrs["exception.message"] = str(result.error)
|
|
123
|
-
|
|
125
|
+
|
|
124
126
|
# Add timestamp
|
|
125
|
-
if hasattr(result,
|
|
127
|
+
if hasattr(result, "endtime") and result.endtime:
|
|
126
128
|
event_attrs["timestamp"] = result.endtime
|
|
127
|
-
|
|
129
|
+
|
|
128
130
|
span.add_event("test.failed", event_attrs)
|
robotframework_tracer/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: robotframework-tracer
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: OpenTelemetry distributed tracing for Robot Framework
|
|
5
5
|
Author: Robot Framework Tracer Contributors
|
|
6
6
|
License: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Documentation, https://github.com/
|
|
9
|
-
Project-URL: Repository, https://github.com/
|
|
10
|
-
Project-URL: Issues, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/tridentsx/robotframework-tracer
|
|
8
|
+
Project-URL: Documentation, https://github.com/tridentsx/robotframework-tracer/blob/main/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/tridentsx/robotframework-tracer
|
|
10
|
+
Project-URL: Issues, https://github.com/tridentsx/robotframework-tracer/issues
|
|
11
11
|
Keywords: robotframework,testing,tracing,opentelemetry,observability
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -49,6 +49,7 @@ This enables you to:
|
|
|
49
49
|
- **Analyze performance** and identify slow keywords or tests
|
|
50
50
|
- **Correlate tests with application traces** in distributed systems
|
|
51
51
|
- **Monitor test execution** across CI/CD pipelines
|
|
52
|
+
- **Propagate trace context** to your System Under Test (SUT)
|
|
52
53
|
|
|
53
54
|

|
|
54
55
|
|
|
@@ -114,6 +115,33 @@ robot --listener robotframework_tracer.TracingListener tests/
|
|
|
114
115
|
|
|
115
116
|
Open http://localhost:16686 in your browser to see your test traces in Jaeger UI.
|
|
116
117
|
|
|
118
|
+
## Trace Context Propagation
|
|
119
|
+
|
|
120
|
+
The tracer automatically makes trace context available as Robot Framework variables for propagating to your System Under Test:
|
|
121
|
+
|
|
122
|
+
```robot
|
|
123
|
+
*** Test Cases ***
|
|
124
|
+
Test API With Distributed Tracing
|
|
125
|
+
# HTTP headers automatically include trace context
|
|
126
|
+
${response}= POST http://my-sut/api
|
|
127
|
+
... json={"data": "test"}
|
|
128
|
+
... headers=${TRACE_HEADERS}
|
|
129
|
+
|
|
130
|
+
# For custom protocols, use individual components
|
|
131
|
+
${diameter_msg}= Create Diameter Request
|
|
132
|
+
... trace_id=${TRACE_ID}
|
|
133
|
+
... span_id=${SPAN_ID}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Available variables:**
|
|
137
|
+
- `${TRACE_HEADERS}` - HTTP headers dictionary
|
|
138
|
+
- `${TRACE_ID}` - 32-character hex trace ID
|
|
139
|
+
- `${SPAN_ID}` - 16-character hex span ID
|
|
140
|
+
- `${TRACEPARENT}` - W3C traceparent header
|
|
141
|
+
- `${TRACESTATE}` - W3C tracestate header
|
|
142
|
+
|
|
143
|
+
See [docs/trace-propagation.md](docs/trace-propagation.md) for complete examples.
|
|
144
|
+
|
|
117
145
|
## Configuration
|
|
118
146
|
|
|
119
147
|
### Basic usage
|
|
@@ -236,7 +264,7 @@ Apache License 2.0 - See [docs/LICENSE](docs/LICENSE) for details.
|
|
|
236
264
|
|
|
237
265
|
## Status
|
|
238
266
|
|
|
239
|
-
**Current Version:** v0.
|
|
240
|
-
**Status:** Production-ready
|
|
267
|
+
**Current Version:** v0.2.0
|
|
268
|
+
**Status:** Production-ready with trace propagation
|
|
241
269
|
|
|
242
270
|
Core functionality is complete and tested. See [docs/CHANGELOG.md](docs/CHANGELOG.md) for version history and [docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md) for the development roadmap.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
robotframework_tracer/__init__.py,sha256=cZh3xnNaYhRisrqqWH5Gdt4zsgpbh1mUALJiUtGNG8M,204
|
|
2
|
+
robotframework_tracer/attributes.py,sha256=_pDHYBJs4QLrnR3Sms7_QyM9IGYHSjZekr-8fkAosBo,3806
|
|
3
|
+
robotframework_tracer/config.py,sha256=4Dis_UlhSaXkO4ln2VeAMIyvFBy_BD3Og9lI9MWXpnA,2530
|
|
4
|
+
robotframework_tracer/listener.py,sha256=WTrK7cNa5hfrru8BpPiu8GCQVzyITtctudFyNAdoPfA,11940
|
|
5
|
+
robotframework_tracer/span_builder.py,sha256=BT6D-fnfwvjGhQihfrg0-BGcZ3EJnEN_bjaTwDHPIqg,4836
|
|
6
|
+
robotframework_tracer/version.py,sha256=HfjVOrpTnmZ-xVFCYSVmX50EXaBQeJteUHG-PD6iQs8,22
|
|
7
|
+
robotframework_tracer-0.2.1.dist-info/METADATA,sha256=_BlwNqM8YeoIGI3V_PuUImCyH3IetyndNXtSJm4yZjA,8617
|
|
8
|
+
robotframework_tracer-0.2.1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
9
|
+
robotframework_tracer-0.2.1.dist-info/top_level.txt,sha256=G1sMKH-8SM_CdJe0Wm6wa_rg1uo62jfhft_UfaxZ05I,22
|
|
10
|
+
robotframework_tracer-0.2.1.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
robotframework_tracer/__init__.py,sha256=cZh3xnNaYhRisrqqWH5Gdt4zsgpbh1mUALJiUtGNG8M,204
|
|
2
|
-
robotframework_tracer/attributes.py,sha256=7lpqy-7maQma01tfG3TzUNLIseQPzARCuYPFiy1K3ao,3854
|
|
3
|
-
robotframework_tracer/config.py,sha256=qCckfLNvwRUrJ8EolG47zJDmZG-9175V7_1gR4ypNZc,2552
|
|
4
|
-
robotframework_tracer/listener.py,sha256=8FMv2BTFo3Q471dFVCoS0YfLrHkeBSPsH6EKG75ccqQ,9795
|
|
5
|
-
robotframework_tracer/span_builder.py,sha256=NKo0AOS-ZxYY_Z6Oypfbg33CAt2EKt6016Vh8woDtfo,4914
|
|
6
|
-
robotframework_tracer/version.py,sha256=rnObPjuBcEStqSO0S6gsdS_ot8ITOQjVj_-P1LUUYpg,22
|
|
7
|
-
robotframework_tracer-0.1.0.dist-info/METADATA,sha256=C60xEMX1xrxLxefDlGggCCzbwd1Nbt_mJI5mXQdyLOY,7654
|
|
8
|
-
robotframework_tracer-0.1.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
9
|
-
robotframework_tracer-0.1.0.dist-info/top_level.txt,sha256=G1sMKH-8SM_CdJe0Wm6wa_rg1uo62jfhft_UfaxZ05I,22
|
|
10
|
-
robotframework_tracer-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
{robotframework_tracer-0.1.0.dist-info → robotframework_tracer-0.2.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|