robotframework-tracer 0.1.0__tar.gz

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.
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.1
2
+ Name: robotframework-tracer
3
+ Version: 0.1.0
4
+ Summary: OpenTelemetry distributed tracing for Robot Framework
5
+ Author: Robot Framework Tracer Contributors
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/yourusername/robotframework-tracer
8
+ Project-URL: Documentation, https://github.com/yourusername/robotframework-tracer/blob/main/README.md
9
+ Project-URL: Repository, https://github.com/yourusername/robotframework-tracer
10
+ Project-URL: Issues, https://github.com/yourusername/robotframework-tracer/issues
11
+ Keywords: robotframework,testing,tracing,opentelemetry,observability
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Framework :: Robot Framework
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: robotframework>=6.0
26
+ Requires-Dist: opentelemetry-api>=1.20.0
27
+ Requires-Dist: opentelemetry-sdk>=1.20.0
28
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
+ Requires-Dist: black>=23.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
34
+ Requires-Dist: mypy>=1.0; extra == "dev"
35
+ Provides-Extra: grpc
36
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0; extra == "grpc"
37
+
38
+ # Robot Framework Tracer
39
+
40
+ OpenTelemetry distributed tracing integration for Robot Framework test execution.
41
+
42
+ ## What is this?
43
+
44
+ `robotframework-tracer` is a Robot Framework listener plugin that automatically creates distributed traces for your test execution using OpenTelemetry. It captures the complete test hierarchy (suites → tests → keywords) as spans and exports them to any OpenTelemetry-compatible backend like Jaeger, Grafana Tempo, or Zipkin.
45
+
46
+ This enables you to:
47
+ - **Visualize test execution flow** with detailed timing information
48
+ - **Debug test failures** by examining the complete execution trace
49
+ - **Analyze performance** and identify slow keywords or tests
50
+ - **Correlate tests with application traces** in distributed systems
51
+ - **Monitor test execution** across CI/CD pipelines
52
+
53
+ ![Robot Framework Trace Visualization](docs/robotframework-trace.jpg)
54
+
55
+ ## How it works
56
+
57
+ The tracer implements the Robot Framework Listener v3 API and creates OpenTelemetry spans for each test execution phase:
58
+
59
+ ```
60
+ Suite Span (root)
61
+ ├── Test Case Span
62
+ │ ├── Keyword Span
63
+ │ │ └── Nested Keyword Span
64
+ │ └── Keyword Span
65
+ └── Test Case Span
66
+ └── Keyword Span
67
+ ```
68
+
69
+ Each span includes rich metadata: test names, tags, status (PASS/FAIL), timing, arguments, and error details.
70
+
71
+ ## Installation
72
+
73
+ ### From PyPI (when released)
74
+
75
+ ```bash
76
+ pip install robotframework-tracer
77
+ ```
78
+
79
+ ### From Source (Development)
80
+
81
+ ```bash
82
+ # Clone the repository
83
+ git clone <repository-url>
84
+ cd robotframework-tracer
85
+
86
+ # Create and activate virtual environment
87
+ python3 -m venv venv
88
+ source venv/bin/activate # On Windows: venv\Scripts\activate
89
+
90
+ # Install in development mode
91
+ pip install -e ".[dev]"
92
+ ```
93
+
94
+ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for detailed development setup instructions.
95
+
96
+ ## Quick Start
97
+
98
+ ### 1. Start a tracing backend (Jaeger example)
99
+
100
+ ```bash
101
+ docker run -d --name jaeger \
102
+ -p 16686:16686 \
103
+ -p 4318:4318 \
104
+ jaegertracing/all-in-one:latest
105
+ ```
106
+
107
+ ### 2. Run your tests with the listener
108
+
109
+ ```bash
110
+ robot --listener robotframework_tracer.TracingListener tests/
111
+ ```
112
+
113
+ ### 3. View traces
114
+
115
+ Open http://localhost:16686 in your browser to see your test traces in Jaeger UI.
116
+
117
+ ## Configuration
118
+
119
+ ### Basic usage
120
+
121
+ ```bash
122
+ robot --listener robotframework_tracer.TracingListener tests/
123
+ ```
124
+
125
+ ### Custom endpoint
126
+
127
+ ```bash
128
+ robot --listener robotframework_tracer.TracingListener:endpoint=http://jaeger:4318/v1/traces tests/
129
+ ```
130
+
131
+ ### Custom service name
132
+
133
+ ```bash
134
+ robot --listener "robotframework_tracer.TracingListener:endpoint=http://jaeger:4318/v1/traces,service_name=my-tests" tests/
135
+ ```
136
+
137
+ ### All configuration options
138
+
139
+ ```bash
140
+ robot --listener "robotframework_tracer.TracingListener:\
141
+ endpoint=http://localhost:4318/v1/traces,\
142
+ service_name=robot-tests,\
143
+ protocol=http,\
144
+ capture_arguments=true,\
145
+ max_arg_length=200" tests/
146
+ ```
147
+
148
+ ### Environment variables
149
+
150
+ ```bash
151
+ export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
152
+ export OTEL_SERVICE_NAME=robot-framework-tests
153
+ robot --listener robotframework_tracer.TracingListener tests/
154
+ ```
155
+
156
+ ## Configuration Options
157
+
158
+ | Option | Default | Description |
159
+ |--------|---------|-------------|
160
+ | `endpoint` | `http://localhost:4318/v1/traces` | OTLP endpoint URL |
161
+ | `service_name` | `rf` | Service name in traces |
162
+ | `protocol` | `http` | Protocol: `http` or `grpc` |
163
+ | `span_prefix_style` | `none` | Span prefix style: `none`, `text`, `emoji` |
164
+ | `capture_arguments` | `true` | Capture keyword arguments |
165
+ | `max_arg_length` | `200` | Max length for arguments |
166
+ | `capture_logs` | `false` | Capture log messages as events |
167
+ | `log_level` | `INFO` | Minimum log level (DEBUG, INFO, WARN, ERROR) |
168
+ | `max_log_length` | `500` | Max length for log messages |
169
+ | `sample_rate` | `1.0` | Sampling rate (0.0-1.0, 1.0 = no sampling) |
170
+
171
+ ## Span Attributes
172
+
173
+ Each span includes relevant Robot Framework metadata:
174
+
175
+ **Suite spans:**
176
+ - `rf.suite.name` - Suite name
177
+ - `rf.suite.source` - Suite file path
178
+ - `rf.suite.id` - Suite ID
179
+ - `rf.version` - Robot Framework version
180
+
181
+ **Test spans:**
182
+ - `rf.test.name` - Test case name
183
+ - `rf.test.id` - Test ID
184
+ - `rf.test.tags` - Test tags
185
+ - `rf.status` - PASS/FAIL/SKIP
186
+ - `rf.elapsed_time` - Execution time
187
+
188
+ **Keyword spans:**
189
+ - `rf.keyword.name` - Keyword name
190
+ - `rf.keyword.type` - SETUP/TEARDOWN/KEYWORD
191
+ - `rf.keyword.library` - Library name
192
+ - `rf.keyword.args` - Arguments (if enabled)
193
+ - `rf.status` - PASS/FAIL
194
+
195
+ ## Supported Backends
196
+
197
+ Works with any OpenTelemetry-compatible backend:
198
+ - **Jaeger** - Open source tracing platform
199
+ - **Grafana Tempo** - High-scale distributed tracing
200
+ - **Zipkin** - Distributed tracing system
201
+ - **AWS X-Ray** - AWS distributed tracing
202
+ - **Honeycomb** - Observability platform
203
+ - **Datadog** - Monitoring and analytics
204
+
205
+ See [docs/backends.md](docs/backends.md) for backend-specific setup guides.
206
+
207
+ ## Requirements
208
+
209
+ - Python 3.8+
210
+ - Robot Framework 6.0+
211
+ - OpenTelemetry SDK
212
+
213
+ ## Documentation
214
+
215
+ - [Architecture](docs/ARCHITECTURE.md) - Design and architecture details
216
+ - [Implementation Plan](docs/IMPLEMENTATION_PLAN.md) - Development roadmap
217
+ - [Configuration Guide](docs/configuration.md) - Detailed configuration reference
218
+ - [Attribute Reference](docs/attributes.md) - Complete attribute documentation
219
+ - [Backend Setup](docs/backends.md) - Backend-specific guides
220
+
221
+ ## Examples
222
+
223
+ See the [examples/](examples/) directory for complete examples:
224
+ - Basic usage with Jaeger
225
+ - Advanced configuration
226
+ - CI/CD integration
227
+ - Multiple backend setups
228
+
229
+ ## Contributing
230
+
231
+ Contributions are welcome! Please see [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines.
232
+
233
+ ## License
234
+
235
+ Apache License 2.0 - See [docs/LICENSE](docs/LICENSE) for details.
236
+
237
+ ## Status
238
+
239
+ **Current Version:** v0.1.0
240
+ **Status:** Production-ready MVP
241
+
242
+ 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,205 @@
1
+ # Robot Framework Tracer
2
+
3
+ OpenTelemetry distributed tracing integration for Robot Framework test execution.
4
+
5
+ ## What is this?
6
+
7
+ `robotframework-tracer` is a Robot Framework listener plugin that automatically creates distributed traces for your test execution using OpenTelemetry. It captures the complete test hierarchy (suites → tests → keywords) as spans and exports them to any OpenTelemetry-compatible backend like Jaeger, Grafana Tempo, or Zipkin.
8
+
9
+ This enables you to:
10
+ - **Visualize test execution flow** with detailed timing information
11
+ - **Debug test failures** by examining the complete execution trace
12
+ - **Analyze performance** and identify slow keywords or tests
13
+ - **Correlate tests with application traces** in distributed systems
14
+ - **Monitor test execution** across CI/CD pipelines
15
+
16
+ ![Robot Framework Trace Visualization](docs/robotframework-trace.jpg)
17
+
18
+ ## How it works
19
+
20
+ The tracer implements the Robot Framework Listener v3 API and creates OpenTelemetry spans for each test execution phase:
21
+
22
+ ```
23
+ Suite Span (root)
24
+ ├── Test Case Span
25
+ │ ├── Keyword Span
26
+ │ │ └── Nested Keyword Span
27
+ │ └── Keyword Span
28
+ └── Test Case Span
29
+ └── Keyword Span
30
+ ```
31
+
32
+ Each span includes rich metadata: test names, tags, status (PASS/FAIL), timing, arguments, and error details.
33
+
34
+ ## Installation
35
+
36
+ ### From PyPI (when released)
37
+
38
+ ```bash
39
+ pip install robotframework-tracer
40
+ ```
41
+
42
+ ### From Source (Development)
43
+
44
+ ```bash
45
+ # Clone the repository
46
+ git clone <repository-url>
47
+ cd robotframework-tracer
48
+
49
+ # Create and activate virtual environment
50
+ python3 -m venv venv
51
+ source venv/bin/activate # On Windows: venv\Scripts\activate
52
+
53
+ # Install in development mode
54
+ pip install -e ".[dev]"
55
+ ```
56
+
57
+ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for detailed development setup instructions.
58
+
59
+ ## Quick Start
60
+
61
+ ### 1. Start a tracing backend (Jaeger example)
62
+
63
+ ```bash
64
+ docker run -d --name jaeger \
65
+ -p 16686:16686 \
66
+ -p 4318:4318 \
67
+ jaegertracing/all-in-one:latest
68
+ ```
69
+
70
+ ### 2. Run your tests with the listener
71
+
72
+ ```bash
73
+ robot --listener robotframework_tracer.TracingListener tests/
74
+ ```
75
+
76
+ ### 3. View traces
77
+
78
+ Open http://localhost:16686 in your browser to see your test traces in Jaeger UI.
79
+
80
+ ## Configuration
81
+
82
+ ### Basic usage
83
+
84
+ ```bash
85
+ robot --listener robotframework_tracer.TracingListener tests/
86
+ ```
87
+
88
+ ### Custom endpoint
89
+
90
+ ```bash
91
+ robot --listener robotframework_tracer.TracingListener:endpoint=http://jaeger:4318/v1/traces tests/
92
+ ```
93
+
94
+ ### Custom service name
95
+
96
+ ```bash
97
+ robot --listener "robotframework_tracer.TracingListener:endpoint=http://jaeger:4318/v1/traces,service_name=my-tests" tests/
98
+ ```
99
+
100
+ ### All configuration options
101
+
102
+ ```bash
103
+ robot --listener "robotframework_tracer.TracingListener:\
104
+ endpoint=http://localhost:4318/v1/traces,\
105
+ service_name=robot-tests,\
106
+ protocol=http,\
107
+ capture_arguments=true,\
108
+ max_arg_length=200" tests/
109
+ ```
110
+
111
+ ### Environment variables
112
+
113
+ ```bash
114
+ export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
115
+ export OTEL_SERVICE_NAME=robot-framework-tests
116
+ robot --listener robotframework_tracer.TracingListener tests/
117
+ ```
118
+
119
+ ## Configuration Options
120
+
121
+ | Option | Default | Description |
122
+ |--------|---------|-------------|
123
+ | `endpoint` | `http://localhost:4318/v1/traces` | OTLP endpoint URL |
124
+ | `service_name` | `rf` | Service name in traces |
125
+ | `protocol` | `http` | Protocol: `http` or `grpc` |
126
+ | `span_prefix_style` | `none` | Span prefix style: `none`, `text`, `emoji` |
127
+ | `capture_arguments` | `true` | Capture keyword arguments |
128
+ | `max_arg_length` | `200` | Max length for arguments |
129
+ | `capture_logs` | `false` | Capture log messages as events |
130
+ | `log_level` | `INFO` | Minimum log level (DEBUG, INFO, WARN, ERROR) |
131
+ | `max_log_length` | `500` | Max length for log messages |
132
+ | `sample_rate` | `1.0` | Sampling rate (0.0-1.0, 1.0 = no sampling) |
133
+
134
+ ## Span Attributes
135
+
136
+ Each span includes relevant Robot Framework metadata:
137
+
138
+ **Suite spans:**
139
+ - `rf.suite.name` - Suite name
140
+ - `rf.suite.source` - Suite file path
141
+ - `rf.suite.id` - Suite ID
142
+ - `rf.version` - Robot Framework version
143
+
144
+ **Test spans:**
145
+ - `rf.test.name` - Test case name
146
+ - `rf.test.id` - Test ID
147
+ - `rf.test.tags` - Test tags
148
+ - `rf.status` - PASS/FAIL/SKIP
149
+ - `rf.elapsed_time` - Execution time
150
+
151
+ **Keyword spans:**
152
+ - `rf.keyword.name` - Keyword name
153
+ - `rf.keyword.type` - SETUP/TEARDOWN/KEYWORD
154
+ - `rf.keyword.library` - Library name
155
+ - `rf.keyword.args` - Arguments (if enabled)
156
+ - `rf.status` - PASS/FAIL
157
+
158
+ ## Supported Backends
159
+
160
+ Works with any OpenTelemetry-compatible backend:
161
+ - **Jaeger** - Open source tracing platform
162
+ - **Grafana Tempo** - High-scale distributed tracing
163
+ - **Zipkin** - Distributed tracing system
164
+ - **AWS X-Ray** - AWS distributed tracing
165
+ - **Honeycomb** - Observability platform
166
+ - **Datadog** - Monitoring and analytics
167
+
168
+ See [docs/backends.md](docs/backends.md) for backend-specific setup guides.
169
+
170
+ ## Requirements
171
+
172
+ - Python 3.8+
173
+ - Robot Framework 6.0+
174
+ - OpenTelemetry SDK
175
+
176
+ ## Documentation
177
+
178
+ - [Architecture](docs/ARCHITECTURE.md) - Design and architecture details
179
+ - [Implementation Plan](docs/IMPLEMENTATION_PLAN.md) - Development roadmap
180
+ - [Configuration Guide](docs/configuration.md) - Detailed configuration reference
181
+ - [Attribute Reference](docs/attributes.md) - Complete attribute documentation
182
+ - [Backend Setup](docs/backends.md) - Backend-specific guides
183
+
184
+ ## Examples
185
+
186
+ See the [examples/](examples/) directory for complete examples:
187
+ - Basic usage with Jaeger
188
+ - Advanced configuration
189
+ - CI/CD integration
190
+ - Multiple backend setups
191
+
192
+ ## Contributing
193
+
194
+ Contributions are welcome! Please see [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines.
195
+
196
+ ## License
197
+
198
+ Apache License 2.0 - See [docs/LICENSE](docs/LICENSE) for details.
199
+
200
+ ## Status
201
+
202
+ **Current Version:** v0.1.0
203
+ **Status:** Production-ready MVP
204
+
205
+ 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,79 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "robotframework-tracer"
7
+ version = "0.1.0"
8
+ description = "OpenTelemetry distributed tracing for Robot Framework"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "Apache-2.0"}
12
+ authors = [
13
+ {name = "Robot Framework Tracer Contributors"}
14
+ ]
15
+ keywords = ["robotframework", "testing", "tracing", "opentelemetry", "observability"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Topic :: Software Development :: Testing",
20
+ "License :: OSI Approved :: Apache Software License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Framework :: Robot Framework",
28
+ ]
29
+ dependencies = [
30
+ "robotframework>=6.0",
31
+ "opentelemetry-api>=1.20.0",
32
+ "opentelemetry-sdk>=1.20.0",
33
+ "opentelemetry-exporter-otlp-proto-http>=1.20.0",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=7.0",
39
+ "pytest-cov>=4.0",
40
+ "black>=23.0",
41
+ "ruff>=0.1.0",
42
+ "mypy>=1.0",
43
+ ]
44
+ grpc = [
45
+ "opentelemetry-exporter-otlp-proto-grpc>=1.20.0",
46
+ ]
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/yourusername/robotframework-tracer"
50
+ Documentation = "https://github.com/yourusername/robotframework-tracer/blob/main/README.md"
51
+ Repository = "https://github.com/yourusername/robotframework-tracer"
52
+ Issues = "https://github.com/yourusername/robotframework-tracer/issues"
53
+
54
+ [tool.setuptools.packages.find]
55
+ where = ["src"]
56
+
57
+ [tool.black]
58
+ line-length = 100
59
+ target-version = ["py38", "py39", "py310", "py311", "py312"]
60
+
61
+ [tool.ruff]
62
+ line-length = 100
63
+ target-version = "py38"
64
+ select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "PT"]
65
+ ignore = ["E501"]
66
+
67
+ [tool.mypy]
68
+ python_version = "3.8"
69
+ warn_return_any = true
70
+ warn_unused_configs = true
71
+ disallow_untyped_defs = false
72
+ disallow_incomplete_defs = false
73
+
74
+ [tool.pytest.ini_options]
75
+ testpaths = ["tests"]
76
+ python_files = ["test_*.py"]
77
+ python_classes = ["Test*"]
78
+ python_functions = ["test_*"]
79
+ addopts = "--cov=src/robotframework_tracer --cov-report=html --cov-report=term-missing"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -0,0 +1,6 @@
1
+ """Robot Framework Tracer - OpenTelemetry distributed tracing for Robot Framework."""
2
+
3
+ from .listener import TracingListener
4
+ from .version import __version__
5
+
6
+ __all__ = ["TracingListener", "__version__"]
@@ -0,0 +1,108 @@
1
+ class RFAttributes:
2
+ """OpenTelemetry semantic conventions for Robot Framework."""
3
+
4
+ # Suite attributes
5
+ SUITE_NAME = "rf.suite.name"
6
+ SUITE_SOURCE = "rf.suite.source"
7
+ SUITE_ID = "rf.suite.id"
8
+ SUITE_METADATA = "rf.suite.metadata"
9
+
10
+ # Test attributes
11
+ TEST_NAME = "rf.test.name"
12
+ TEST_ID = "rf.test.id"
13
+ TEST_TAGS = "rf.test.tags"
14
+ TEST_TEMPLATE = "rf.test.template"
15
+ TEST_TIMEOUT = "rf.test.timeout"
16
+
17
+ # Keyword attributes
18
+ KEYWORD_NAME = "rf.keyword.name"
19
+ KEYWORD_TYPE = "rf.keyword.type"
20
+ KEYWORD_LIBRARY = "rf.keyword.library"
21
+ KEYWORD_ARGS = "rf.keyword.args"
22
+
23
+ # Result attributes
24
+ STATUS = "rf.status"
25
+ ELAPSED_TIME = "rf.elapsed_time"
26
+ START_TIME = "rf.start_time"
27
+ END_TIME = "rf.end_time"
28
+ MESSAGE = "rf.message"
29
+
30
+ # Framework attributes
31
+ RF_VERSION = "rf.version"
32
+
33
+
34
+ class AttributeExtractor:
35
+ """Extract attributes from Robot Framework objects."""
36
+
37
+ @staticmethod
38
+ def from_suite(data, result):
39
+ """Extract attributes from suite data and result."""
40
+ attrs = {
41
+ RFAttributes.SUITE_NAME: data.name,
42
+ RFAttributes.SUITE_ID: result.id,
43
+ }
44
+ if data.source:
45
+ attrs[RFAttributes.SUITE_SOURCE] = str(data.source)
46
+
47
+ # Extract suite metadata
48
+ if hasattr(data, 'metadata') and data.metadata:
49
+ for key, value in data.metadata.items():
50
+ attrs[f"{RFAttributes.SUITE_METADATA}.{key}"] = str(value)
51
+
52
+ # Add timing information
53
+ if hasattr(result, 'starttime') and result.starttime:
54
+ attrs[RFAttributes.START_TIME] = result.starttime
55
+ if hasattr(result, 'endtime') and result.endtime:
56
+ attrs[RFAttributes.END_TIME] = result.endtime
57
+
58
+ return attrs
59
+
60
+ @staticmethod
61
+ def from_test(data, result):
62
+ """Extract attributes from test data and result."""
63
+ attrs = {
64
+ RFAttributes.TEST_NAME: data.name,
65
+ RFAttributes.TEST_ID: result.id,
66
+ }
67
+ if data.tags:
68
+ attrs[RFAttributes.TEST_TAGS] = list(data.tags)
69
+
70
+ # Add test template if available
71
+ if hasattr(data, 'template') and data.template:
72
+ attrs[RFAttributes.TEST_TEMPLATE] = str(data.template)
73
+
74
+ # Add test timeout if available
75
+ if hasattr(data, 'timeout') and data.timeout:
76
+ attrs[RFAttributes.TEST_TIMEOUT] = str(data.timeout)
77
+
78
+ # Add timing information
79
+ if hasattr(result, 'starttime') and result.starttime:
80
+ attrs[RFAttributes.START_TIME] = result.starttime
81
+ if hasattr(result, 'endtime') and result.endtime:
82
+ attrs[RFAttributes.END_TIME] = result.endtime
83
+
84
+ # Add message if available
85
+ if hasattr(result, 'message') and result.message:
86
+ attrs[RFAttributes.MESSAGE] = result.message
87
+
88
+ return attrs
89
+
90
+ @staticmethod
91
+ def from_keyword(data, result, max_arg_length=200):
92
+ """Extract attributes from keyword data and result."""
93
+ attrs = {
94
+ RFAttributes.KEYWORD_NAME: data.name,
95
+ RFAttributes.KEYWORD_TYPE: data.type,
96
+ }
97
+ # Try to get library name (may not always be available)
98
+ if hasattr(data, 'libname') and data.libname:
99
+ attrs[RFAttributes.KEYWORD_LIBRARY] = data.libname
100
+ elif hasattr(data, 'owner') and data.owner:
101
+ attrs[RFAttributes.KEYWORD_LIBRARY] = data.owner.name if hasattr(data.owner, 'name') else str(data.owner)
102
+
103
+ if data.args:
104
+ args_str = ", ".join(str(arg)[:max_arg_length] for arg in data.args[:10])
105
+ if len(args_str) > max_arg_length:
106
+ args_str = args_str[:max_arg_length] + "..."
107
+ attrs[RFAttributes.KEYWORD_ARGS] = args_str
108
+ return attrs
@@ -0,0 +1,63 @@
1
+ import os
2
+
3
+
4
+ class TracerConfig:
5
+ """Configuration for the Robot Framework tracer."""
6
+
7
+ def __init__(self, **kwargs):
8
+ self.endpoint = self._get_config(
9
+ "endpoint", kwargs, "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318/v1/traces"
10
+ )
11
+ self.service_name = self._get_config(
12
+ "service_name", kwargs, "OTEL_SERVICE_NAME", "rf"
13
+ )
14
+ self.protocol = self._get_config("protocol", kwargs, "RF_TRACER_PROTOCOL", "http")
15
+ self.capture_arguments = self._get_bool_config(
16
+ "capture_arguments", kwargs, "RF_TRACER_CAPTURE_ARGUMENTS", True
17
+ )
18
+ self.max_arg_length = int(
19
+ self._get_config("max_arg_length", kwargs, "RF_TRACER_MAX_ARG_LENGTH", "200")
20
+ )
21
+ self.capture_logs = self._get_bool_config(
22
+ "capture_logs", kwargs, "RF_TRACER_CAPTURE_LOGS", False
23
+ )
24
+ self.log_level = self._get_config(
25
+ "log_level", kwargs, "RF_TRACER_LOG_LEVEL", "INFO"
26
+ ).upper()
27
+ self.max_log_length = int(
28
+ self._get_config("max_log_length", kwargs, "RF_TRACER_MAX_LOG_LENGTH", "500")
29
+ )
30
+ self.sample_rate = float(
31
+ self._get_config("sample_rate", kwargs, "RF_TRACER_SAMPLE_RATE", "1.0")
32
+ )
33
+ self.span_prefix_style = self._get_config(
34
+ "span_prefix_style", kwargs, "RF_TRACER_SPAN_PREFIX_STYLE", "none"
35
+ ).lower()
36
+
37
+ def _get_config(self, key, kwargs, env_var, default):
38
+ """Get configuration value with precedence: kwargs > env > default."""
39
+ return kwargs.get(key, os.environ.get(env_var, default))
40
+
41
+ def _get_bool_config(self, key, kwargs, env_var, default):
42
+ """Get boolean configuration value."""
43
+ value = self._get_config(key, kwargs, env_var, str(default))
44
+ if isinstance(value, bool):
45
+ return value
46
+ return value.lower() in ("true", "1", "yes")
47
+
48
+ @staticmethod
49
+ def from_listener_args(*args):
50
+ """Parse listener arguments from Robot Framework.
51
+
52
+ Args can be passed as:
53
+ - Single string: "endpoint=http://localhost:4318,service_name=my-service"
54
+ - Multiple strings: "endpoint=http://localhost:4318", "service_name=my-service"
55
+ """
56
+ kwargs = {}
57
+ for arg in args:
58
+ if "=" in arg:
59
+ for pair in arg.split(","):
60
+ if "=" in pair:
61
+ key, value = pair.split("=", 1)
62
+ kwargs[key.strip()] = value.strip()
63
+ return TracerConfig(**kwargs)
@@ -0,0 +1,236 @@
1
+ from opentelemetry import trace
2
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPExporter
3
+ from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_VERSION
4
+ from opentelemetry.sdk.trace import TracerProvider
5
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
6
+ from opentelemetry.sdk.trace.sampling import TraceIdRatioBased, ParentBased
7
+ from opentelemetry.semconv.resource import ResourceAttributes
8
+ import platform
9
+ import sys
10
+ import robot
11
+
12
+ from .config import TracerConfig
13
+ from .span_builder import SpanBuilder
14
+
15
+ # Try to import gRPC exporter (optional dependency)
16
+ try:
17
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GRPCExporter
18
+ GRPC_AVAILABLE = True
19
+ except ImportError:
20
+ GRPC_AVAILABLE = False
21
+
22
+
23
+ class TracingListener:
24
+ """Robot Framework Listener v3 for distributed tracing."""
25
+
26
+ ROBOT_LISTENER_API_VERSION = 3
27
+
28
+ def __init__(self, endpoint=None, service_name=None, protocol=None,
29
+ capture_arguments=None, max_arg_length=None, capture_logs=None,
30
+ sample_rate=None, span_prefix_style=None, log_level=None, max_log_length=None):
31
+ """Initialize the tracing listener.
32
+
33
+ Args:
34
+ endpoint: OTLP endpoint URL
35
+ service_name: Service name for traces
36
+ protocol: Protocol (http or grpc)
37
+ capture_arguments: Whether to capture keyword arguments
38
+ max_arg_length: Maximum length for arguments
39
+ capture_logs: Whether to capture log messages
40
+ sample_rate: Sampling rate (0.0-1.0)
41
+ span_prefix_style: Span prefix style (none, text, emoji)
42
+ log_level: Minimum log level to capture (DEBUG, INFO, WARN, ERROR)
43
+ max_log_length: Maximum length for log messages
44
+ """
45
+ # Build kwargs dict from provided arguments
46
+ kwargs = {}
47
+ if endpoint is not None:
48
+ kwargs['endpoint'] = endpoint
49
+ if service_name is not None:
50
+ kwargs['service_name'] = service_name
51
+ if protocol is not None:
52
+ kwargs['protocol'] = protocol
53
+ if capture_arguments is not None:
54
+ kwargs['capture_arguments'] = capture_arguments
55
+ if max_arg_length is not None:
56
+ kwargs['max_arg_length'] = max_arg_length
57
+ if capture_logs is not None:
58
+ kwargs['capture_logs'] = capture_logs
59
+ if sample_rate is not None:
60
+ kwargs['sample_rate'] = sample_rate
61
+ if span_prefix_style is not None:
62
+ kwargs['span_prefix_style'] = span_prefix_style
63
+ if log_level is not None:
64
+ kwargs['log_level'] = log_level
65
+ if max_log_length is not None:
66
+ kwargs['max_log_length'] = max_log_length
67
+
68
+ self.config = TracerConfig(**kwargs)
69
+
70
+ # Initialize OpenTelemetry with automatic resource detection
71
+ resource_attrs = {
72
+ SERVICE_NAME: self.config.service_name,
73
+ ResourceAttributes.TELEMETRY_SDK_NAME: "robotframework-tracer",
74
+ ResourceAttributes.TELEMETRY_SDK_LANGUAGE: "python",
75
+ ResourceAttributes.TELEMETRY_SDK_VERSION: "0.1.0",
76
+ "rf.version": robot.version.get_version(),
77
+ "python.version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
78
+ ResourceAttributes.HOST_NAME: platform.node(),
79
+ ResourceAttributes.OS_TYPE: platform.system(),
80
+ ResourceAttributes.OS_VERSION: platform.release(),
81
+ }
82
+ resource = Resource.create(resource_attrs)
83
+
84
+ # Configure sampling only if sample_rate < 1.0
85
+ if self.config.sample_rate < 1.0:
86
+ sampler = ParentBased(root=TraceIdRatioBased(self.config.sample_rate))
87
+ provider = TracerProvider(resource=resource, sampler=sampler)
88
+ else:
89
+ provider = TracerProvider(resource=resource)
90
+
91
+ # Select exporter based on protocol
92
+ if self.config.protocol == "grpc":
93
+ if not GRPC_AVAILABLE:
94
+ print("Warning: gRPC exporter not available. Install with: pip install opentelemetry-exporter-otlp-proto-grpc")
95
+ print("Falling back to HTTP exporter")
96
+ exporter = HTTPExporter(endpoint=self.config.endpoint)
97
+ else:
98
+ exporter = GRPCExporter(endpoint=self.config.endpoint)
99
+ else:
100
+ exporter = HTTPExporter(endpoint=self.config.endpoint)
101
+
102
+ processor = BatchSpanProcessor(exporter)
103
+ provider.add_span_processor(processor)
104
+ trace.set_tracer_provider(provider)
105
+
106
+ self.tracer = trace.get_tracer(__name__)
107
+ self.span_stack = []
108
+
109
+ def start_suite(self, data, result):
110
+ """Create root span for suite."""
111
+ try:
112
+ span = SpanBuilder.create_suite_span(self.tracer, data, result, self.config.span_prefix_style)
113
+ self.span_stack.append(span)
114
+ except Exception as e:
115
+ print(f"TracingListener error in start_suite: {e}")
116
+
117
+ def end_suite(self, data, result):
118
+ """Close suite span."""
119
+ try:
120
+ if self.span_stack:
121
+ span = self.span_stack.pop()
122
+ SpanBuilder.set_span_status(span, result)
123
+ span.end()
124
+ except Exception as e:
125
+ print(f"TracingListener error in end_suite: {e}")
126
+
127
+ def start_test(self, data, result):
128
+ """Create child span for test case."""
129
+ try:
130
+ parent_context = (
131
+ trace.set_span_in_context(self.span_stack[-1]) if self.span_stack else None
132
+ )
133
+ span = SpanBuilder.create_test_span(self.tracer, data, result, parent_context, self.config.span_prefix_style)
134
+ self.span_stack.append(span)
135
+ except Exception as e:
136
+ print(f"TracingListener error in start_test: {e}")
137
+
138
+ def end_test(self, data, result):
139
+ """Close test span with verdict."""
140
+ try:
141
+ if self.span_stack:
142
+ span = self.span_stack.pop()
143
+ SpanBuilder.set_span_status(span, result)
144
+ if result.status == "FAIL":
145
+ SpanBuilder.add_error_event(span, result)
146
+ span.end()
147
+ except Exception as e:
148
+ print(f"TracingListener error in end_test: {e}")
149
+
150
+ def start_keyword(self, data, result):
151
+ """Create child span for keyword/step."""
152
+ try:
153
+ if not self.config.capture_arguments and data.args:
154
+ return
155
+
156
+ parent_context = (
157
+ trace.set_span_in_context(self.span_stack[-1]) if self.span_stack else None
158
+ )
159
+ span = SpanBuilder.create_keyword_span(
160
+ self.tracer, data, result, parent_context, self.config.max_arg_length, self.config.span_prefix_style
161
+ )
162
+ self.span_stack.append(span)
163
+
164
+ # Add event for setup/teardown start
165
+ if data.type in ("SETUP", "TEARDOWN"):
166
+ span.add_event(f"{data.type.lower()}.start", {"keyword": data.name})
167
+ except Exception as e:
168
+ print(f"TracingListener error in start_keyword: {e}")
169
+
170
+ def end_keyword(self, data, result):
171
+ """Close keyword span."""
172
+ try:
173
+ if self.span_stack:
174
+ span = self.span_stack.pop()
175
+
176
+ # Add event for setup/teardown end
177
+ if data.type in ("SETUP", "TEARDOWN"):
178
+ span.add_event(f"{data.type.lower()}.end", {
179
+ "keyword": data.name,
180
+ "status": result.status
181
+ })
182
+
183
+ SpanBuilder.set_span_status(span, result)
184
+ if result.status == "FAIL":
185
+ SpanBuilder.add_error_event(span, result)
186
+ span.end()
187
+ except Exception as e:
188
+ print(f"TracingListener error in end_keyword: {e}")
189
+
190
+ def close(self):
191
+ """Cleanup on listener close."""
192
+ try:
193
+ while self.span_stack:
194
+ span = self.span_stack.pop()
195
+ span.end()
196
+ trace.get_tracer_provider().force_flush()
197
+ except Exception as e:
198
+ print(f"TracingListener error in close: {e}")
199
+
200
+ def log_message(self, message):
201
+ """Capture log messages as span events."""
202
+ try:
203
+ if not self.config.capture_logs or not self.span_stack:
204
+ return
205
+
206
+ # Filter by log level
207
+ log_levels = {"TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, "ERROR": 4, "FAIL": 5}
208
+ min_level = log_levels.get(self.config.log_level, 2)
209
+ msg_level = log_levels.get(message.level, 2)
210
+
211
+ if msg_level < min_level:
212
+ return
213
+
214
+ # Get current span
215
+ current_span = self.span_stack[-1]
216
+
217
+ # Limit message length
218
+ log_text = message.message
219
+ if len(log_text) > self.config.max_log_length:
220
+ log_text = log_text[:self.config.max_log_length] + "..."
221
+
222
+ # Add log as span event (convert timestamp to string)
223
+ event_attrs = {
224
+ "message": log_text,
225
+ "level": message.level,
226
+ }
227
+ if hasattr(message, 'timestamp') and message.timestamp:
228
+ event_attrs["timestamp"] = str(message.timestamp)
229
+
230
+ current_span.add_event(f"log.{message.level.lower()}", event_attrs)
231
+ except RecursionError:
232
+ # Avoid infinite recursion if logging causes more logs
233
+ pass
234
+ except Exception:
235
+ # Silently ignore errors in log capture to avoid breaking tests
236
+ pass
@@ -0,0 +1,128 @@
1
+ from opentelemetry import trace, baggage
2
+ from opentelemetry.trace import Status, StatusCode
3
+ import robot
4
+
5
+ from .attributes import AttributeExtractor, RFAttributes
6
+
7
+
8
+ class SpanBuilder:
9
+ """Build OpenTelemetry spans from Robot Framework objects."""
10
+
11
+ # Prefix mappings
12
+ TEXT_PREFIXES = {
13
+ "SUITE": "[SUITE]",
14
+ "TEST": "[TEST CASE]",
15
+ "KEYWORD": "[TEST STEP]",
16
+ "SETUP": "[SETUP]",
17
+ "TEARDOWN": "[TEARDOWN]",
18
+ }
19
+
20
+ EMOJI_PREFIXES = {
21
+ "SUITE": "📦",
22
+ "TEST": "🧪",
23
+ "KEYWORD": "👟",
24
+ "SETUP": "🔧",
25
+ "TEARDOWN": "🧹",
26
+ }
27
+
28
+ @staticmethod
29
+ def _add_prefix(name, span_type, prefix_style):
30
+ """Add prefix to span name based on style."""
31
+ if prefix_style == "none" or not prefix_style:
32
+ return name
33
+ elif prefix_style == "text":
34
+ prefix = SpanBuilder.TEXT_PREFIXES.get(span_type, "")
35
+ return f"{prefix} {name}" if prefix else name
36
+ elif prefix_style == "emoji":
37
+ prefix = SpanBuilder.EMOJI_PREFIXES.get(span_type, "")
38
+ return f"{prefix} {name}" if prefix else name
39
+ return name
40
+
41
+ @staticmethod
42
+ def create_suite_span(tracer, data, result, prefix_style="none"):
43
+ """Create root span for test suite."""
44
+ attrs = AttributeExtractor.from_suite(data, result)
45
+ name = SpanBuilder._add_prefix(data.name, "SUITE", prefix_style)
46
+
47
+ # Create context with baggage
48
+ ctx = baggage.set_baggage("rf.suite.id", result.id)
49
+ ctx = baggage.set_baggage("rf.version", robot.version.get_version(), ctx)
50
+
51
+ # Add suite metadata to baggage (limit to avoid too much data)
52
+ if hasattr(data, 'metadata') and data.metadata:
53
+ for key, value in list(data.metadata.items())[:5]: # Limit to 5 metadata items
54
+ ctx = baggage.set_baggage(f"rf.suite.metadata.{key}", str(value), ctx)
55
+
56
+ span = tracer.start_span(name, context=ctx, kind=trace.SpanKind.INTERNAL, attributes=attrs)
57
+ return span
58
+
59
+ @staticmethod
60
+ def create_test_span(tracer, data, result, parent_context, prefix_style="none"):
61
+ """Create child span for test case."""
62
+ attrs = AttributeExtractor.from_test(data, result)
63
+ name = SpanBuilder._add_prefix(data.name, "TEST", prefix_style)
64
+ span = tracer.start_span(
65
+ name, context=parent_context, kind=trace.SpanKind.INTERNAL, attributes=attrs
66
+ )
67
+ return span
68
+
69
+ @staticmethod
70
+ def create_keyword_span(tracer, data, result, parent_context, max_arg_length=200, prefix_style="none"):
71
+ """Create child span for keyword."""
72
+ attrs = AttributeExtractor.from_keyword(data, result, max_arg_length)
73
+
74
+ # Build keyword name with arguments (like RF test step line)
75
+ kw_name = data.name
76
+ if data.args:
77
+ # Join arguments with comma-space
78
+ args_str = ", ".join(str(arg) for arg in data.args)
79
+ # Limit total length to avoid extremely long span names
80
+ if len(args_str) > 100:
81
+ args_str = args_str[:100] + "..."
82
+ kw_name = f"{data.name} {args_str}"
83
+
84
+ # Determine span type for prefix
85
+ if data.type in ("SETUP", "TEARDOWN"):
86
+ span_type = data.type
87
+ else:
88
+ span_type = "KEYWORD"
89
+
90
+ # Add prefix based on style
91
+ kw_name = SpanBuilder._add_prefix(kw_name, span_type, prefix_style)
92
+
93
+ span = tracer.start_span(
94
+ kw_name, context=parent_context, kind=trace.SpanKind.INTERNAL, attributes=attrs
95
+ )
96
+ return span
97
+
98
+ @staticmethod
99
+ def set_span_status(span, result):
100
+ """Set span status based on RF result."""
101
+ span.set_attribute(RFAttributes.STATUS, result.status)
102
+ span.set_attribute(RFAttributes.ELAPSED_TIME, result.elapsedtime / 1000.0)
103
+
104
+ if result.status == "FAIL":
105
+ span.set_status(Status(StatusCode.ERROR, result.message))
106
+ else:
107
+ span.set_status(Status(StatusCode.OK))
108
+
109
+ @staticmethod
110
+ def add_error_event(span, result):
111
+ """Add error event with exception details."""
112
+ if result.status == "FAIL" and result.message:
113
+ # Create detailed error event
114
+ event_attrs = {
115
+ "message": result.message,
116
+ "rf.status": "FAIL",
117
+ }
118
+
119
+ # Try to extract exception type if available
120
+ if hasattr(result, 'error') and result.error:
121
+ event_attrs["exception.type"] = type(result.error).__name__
122
+ event_attrs["exception.message"] = str(result.error)
123
+
124
+ # Add timestamp
125
+ if hasattr(result, 'endtime') and result.endtime:
126
+ event_attrs["timestamp"] = result.endtime
127
+
128
+ span.add_event("test.failed", event_attrs)
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.1
2
+ Name: robotframework-tracer
3
+ Version: 0.1.0
4
+ Summary: OpenTelemetry distributed tracing for Robot Framework
5
+ Author: Robot Framework Tracer Contributors
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/yourusername/robotframework-tracer
8
+ Project-URL: Documentation, https://github.com/yourusername/robotframework-tracer/blob/main/README.md
9
+ Project-URL: Repository, https://github.com/yourusername/robotframework-tracer
10
+ Project-URL: Issues, https://github.com/yourusername/robotframework-tracer/issues
11
+ Keywords: robotframework,testing,tracing,opentelemetry,observability
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Framework :: Robot Framework
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: robotframework>=6.0
26
+ Requires-Dist: opentelemetry-api>=1.20.0
27
+ Requires-Dist: opentelemetry-sdk>=1.20.0
28
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
+ Requires-Dist: black>=23.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
34
+ Requires-Dist: mypy>=1.0; extra == "dev"
35
+ Provides-Extra: grpc
36
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0; extra == "grpc"
37
+
38
+ # Robot Framework Tracer
39
+
40
+ OpenTelemetry distributed tracing integration for Robot Framework test execution.
41
+
42
+ ## What is this?
43
+
44
+ `robotframework-tracer` is a Robot Framework listener plugin that automatically creates distributed traces for your test execution using OpenTelemetry. It captures the complete test hierarchy (suites → tests → keywords) as spans and exports them to any OpenTelemetry-compatible backend like Jaeger, Grafana Tempo, or Zipkin.
45
+
46
+ This enables you to:
47
+ - **Visualize test execution flow** with detailed timing information
48
+ - **Debug test failures** by examining the complete execution trace
49
+ - **Analyze performance** and identify slow keywords or tests
50
+ - **Correlate tests with application traces** in distributed systems
51
+ - **Monitor test execution** across CI/CD pipelines
52
+
53
+ ![Robot Framework Trace Visualization](docs/robotframework-trace.jpg)
54
+
55
+ ## How it works
56
+
57
+ The tracer implements the Robot Framework Listener v3 API and creates OpenTelemetry spans for each test execution phase:
58
+
59
+ ```
60
+ Suite Span (root)
61
+ ├── Test Case Span
62
+ │ ├── Keyword Span
63
+ │ │ └── Nested Keyword Span
64
+ │ └── Keyword Span
65
+ └── Test Case Span
66
+ └── Keyword Span
67
+ ```
68
+
69
+ Each span includes rich metadata: test names, tags, status (PASS/FAIL), timing, arguments, and error details.
70
+
71
+ ## Installation
72
+
73
+ ### From PyPI (when released)
74
+
75
+ ```bash
76
+ pip install robotframework-tracer
77
+ ```
78
+
79
+ ### From Source (Development)
80
+
81
+ ```bash
82
+ # Clone the repository
83
+ git clone <repository-url>
84
+ cd robotframework-tracer
85
+
86
+ # Create and activate virtual environment
87
+ python3 -m venv venv
88
+ source venv/bin/activate # On Windows: venv\Scripts\activate
89
+
90
+ # Install in development mode
91
+ pip install -e ".[dev]"
92
+ ```
93
+
94
+ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for detailed development setup instructions.
95
+
96
+ ## Quick Start
97
+
98
+ ### 1. Start a tracing backend (Jaeger example)
99
+
100
+ ```bash
101
+ docker run -d --name jaeger \
102
+ -p 16686:16686 \
103
+ -p 4318:4318 \
104
+ jaegertracing/all-in-one:latest
105
+ ```
106
+
107
+ ### 2. Run your tests with the listener
108
+
109
+ ```bash
110
+ robot --listener robotframework_tracer.TracingListener tests/
111
+ ```
112
+
113
+ ### 3. View traces
114
+
115
+ Open http://localhost:16686 in your browser to see your test traces in Jaeger UI.
116
+
117
+ ## Configuration
118
+
119
+ ### Basic usage
120
+
121
+ ```bash
122
+ robot --listener robotframework_tracer.TracingListener tests/
123
+ ```
124
+
125
+ ### Custom endpoint
126
+
127
+ ```bash
128
+ robot --listener robotframework_tracer.TracingListener:endpoint=http://jaeger:4318/v1/traces tests/
129
+ ```
130
+
131
+ ### Custom service name
132
+
133
+ ```bash
134
+ robot --listener "robotframework_tracer.TracingListener:endpoint=http://jaeger:4318/v1/traces,service_name=my-tests" tests/
135
+ ```
136
+
137
+ ### All configuration options
138
+
139
+ ```bash
140
+ robot --listener "robotframework_tracer.TracingListener:\
141
+ endpoint=http://localhost:4318/v1/traces,\
142
+ service_name=robot-tests,\
143
+ protocol=http,\
144
+ capture_arguments=true,\
145
+ max_arg_length=200" tests/
146
+ ```
147
+
148
+ ### Environment variables
149
+
150
+ ```bash
151
+ export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
152
+ export OTEL_SERVICE_NAME=robot-framework-tests
153
+ robot --listener robotframework_tracer.TracingListener tests/
154
+ ```
155
+
156
+ ## Configuration Options
157
+
158
+ | Option | Default | Description |
159
+ |--------|---------|-------------|
160
+ | `endpoint` | `http://localhost:4318/v1/traces` | OTLP endpoint URL |
161
+ | `service_name` | `rf` | Service name in traces |
162
+ | `protocol` | `http` | Protocol: `http` or `grpc` |
163
+ | `span_prefix_style` | `none` | Span prefix style: `none`, `text`, `emoji` |
164
+ | `capture_arguments` | `true` | Capture keyword arguments |
165
+ | `max_arg_length` | `200` | Max length for arguments |
166
+ | `capture_logs` | `false` | Capture log messages as events |
167
+ | `log_level` | `INFO` | Minimum log level (DEBUG, INFO, WARN, ERROR) |
168
+ | `max_log_length` | `500` | Max length for log messages |
169
+ | `sample_rate` | `1.0` | Sampling rate (0.0-1.0, 1.0 = no sampling) |
170
+
171
+ ## Span Attributes
172
+
173
+ Each span includes relevant Robot Framework metadata:
174
+
175
+ **Suite spans:**
176
+ - `rf.suite.name` - Suite name
177
+ - `rf.suite.source` - Suite file path
178
+ - `rf.suite.id` - Suite ID
179
+ - `rf.version` - Robot Framework version
180
+
181
+ **Test spans:**
182
+ - `rf.test.name` - Test case name
183
+ - `rf.test.id` - Test ID
184
+ - `rf.test.tags` - Test tags
185
+ - `rf.status` - PASS/FAIL/SKIP
186
+ - `rf.elapsed_time` - Execution time
187
+
188
+ **Keyword spans:**
189
+ - `rf.keyword.name` - Keyword name
190
+ - `rf.keyword.type` - SETUP/TEARDOWN/KEYWORD
191
+ - `rf.keyword.library` - Library name
192
+ - `rf.keyword.args` - Arguments (if enabled)
193
+ - `rf.status` - PASS/FAIL
194
+
195
+ ## Supported Backends
196
+
197
+ Works with any OpenTelemetry-compatible backend:
198
+ - **Jaeger** - Open source tracing platform
199
+ - **Grafana Tempo** - High-scale distributed tracing
200
+ - **Zipkin** - Distributed tracing system
201
+ - **AWS X-Ray** - AWS distributed tracing
202
+ - **Honeycomb** - Observability platform
203
+ - **Datadog** - Monitoring and analytics
204
+
205
+ See [docs/backends.md](docs/backends.md) for backend-specific setup guides.
206
+
207
+ ## Requirements
208
+
209
+ - Python 3.8+
210
+ - Robot Framework 6.0+
211
+ - OpenTelemetry SDK
212
+
213
+ ## Documentation
214
+
215
+ - [Architecture](docs/ARCHITECTURE.md) - Design and architecture details
216
+ - [Implementation Plan](docs/IMPLEMENTATION_PLAN.md) - Development roadmap
217
+ - [Configuration Guide](docs/configuration.md) - Detailed configuration reference
218
+ - [Attribute Reference](docs/attributes.md) - Complete attribute documentation
219
+ - [Backend Setup](docs/backends.md) - Backend-specific guides
220
+
221
+ ## Examples
222
+
223
+ See the [examples/](examples/) directory for complete examples:
224
+ - Basic usage with Jaeger
225
+ - Advanced configuration
226
+ - CI/CD integration
227
+ - Multiple backend setups
228
+
229
+ ## Contributing
230
+
231
+ Contributions are welcome! Please see [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines.
232
+
233
+ ## License
234
+
235
+ Apache License 2.0 - See [docs/LICENSE](docs/LICENSE) for details.
236
+
237
+ ## Status
238
+
239
+ **Current Version:** v0.1.0
240
+ **Status:** Production-ready MVP
241
+
242
+ 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,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ src/robotframework_tracer/__init__.py
5
+ src/robotframework_tracer/attributes.py
6
+ src/robotframework_tracer/config.py
7
+ src/robotframework_tracer/listener.py
8
+ src/robotframework_tracer/span_builder.py
9
+ src/robotframework_tracer/version.py
10
+ src/robotframework_tracer.egg-info/PKG-INFO
11
+ src/robotframework_tracer.egg-info/SOURCES.txt
12
+ src/robotframework_tracer.egg-info/dependency_links.txt
13
+ src/robotframework_tracer.egg-info/requires.txt
14
+ src/robotframework_tracer.egg-info/top_level.txt
@@ -0,0 +1,14 @@
1
+ robotframework>=6.0
2
+ opentelemetry-api>=1.20.0
3
+ opentelemetry-sdk>=1.20.0
4
+ opentelemetry-exporter-otlp-proto-http>=1.20.0
5
+
6
+ [dev]
7
+ pytest>=7.0
8
+ pytest-cov>=4.0
9
+ black>=23.0
10
+ ruff>=0.1.0
11
+ mypy>=1.0
12
+
13
+ [grpc]
14
+ opentelemetry-exporter-otlp-proto-grpc>=1.20.0