spanwire 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.
- spanwire-0.1.0/LICENSE +21 -0
- spanwire-0.1.0/PKG-INFO +226 -0
- spanwire-0.1.0/README.md +199 -0
- spanwire-0.1.0/pyproject.toml +56 -0
- spanwire-0.1.0/setup.cfg +4 -0
- spanwire-0.1.0/src/spanwire/__init__.py +145 -0
- spanwire-0.1.0/src/spanwire/_version.py +1 -0
- spanwire-0.1.0/src/spanwire/breadcrumbs.py +88 -0
- spanwire-0.1.0/src/spanwire/context.py +59 -0
- spanwire-0.1.0/src/spanwire/core.py +463 -0
- spanwire-0.1.0/src/spanwire/exporters.py +188 -0
- spanwire-0.1.0/src/spanwire/py.typed +0 -0
- spanwire-0.1.0/src/spanwire/sampler.py +108 -0
- spanwire-0.1.0/src/spanwire/w3c.py +160 -0
- spanwire-0.1.0/src/spanwire.egg-info/PKG-INFO +226 -0
- spanwire-0.1.0/src/spanwire.egg-info/SOURCES.txt +24 -0
- spanwire-0.1.0/src/spanwire.egg-info/dependency_links.txt +1 -0
- spanwire-0.1.0/src/spanwire.egg-info/top_level.txt +1 -0
- spanwire-0.1.0/tests/test_advanced.py +86 -0
- spanwire-0.1.0/tests/test_breadcrumbs.py +56 -0
- spanwire-0.1.0/tests/test_context.py +83 -0
- spanwire-0.1.0/tests/test_core.py +115 -0
- spanwire-0.1.0/tests/test_decorator.py +45 -0
- spanwire-0.1.0/tests/test_exporters.py +75 -0
- spanwire-0.1.0/tests/test_sampler.py +76 -0
- spanwire-0.1.0/tests/test_w3c.py +122 -0
spanwire-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hamidreza Moghaddam Kohi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
spanwire-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spanwire
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-dependency micro-tracing, distributed context propagation & breadcrumb telemetry engine for Python.
|
|
5
|
+
Author-email: Hamidreza <h4midrezam@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mrhamidrezam/spanwire
|
|
8
|
+
Project-URL: Repository, https://github.com/mrhamidrezam/spanwire
|
|
9
|
+
Project-URL: Issues, https://github.com/mrhamidrezam/spanwire/issues
|
|
10
|
+
Keywords: tracing,distributed-tracing,opentelemetry,w3c,traceparent,baggage,breadcrumbs,telemetry,contextvars,zero-dependency,async
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: System :: Monitoring
|
|
21
|
+
Classifier: Topic :: System :: Logging
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
<div align="center">
|
|
29
|
+
|
|
30
|
+
# ⚡ spanwire
|
|
31
|
+
|
|
32
|
+
**Zero-dependency micro-tracing, distributed context propagation & breadcrumb telemetry engine for Python.**
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/spanwire/)
|
|
35
|
+
[](https://pypi.org/project/spanwire/)
|
|
36
|
+
[](https://opensource.org/licenses/MIT)
|
|
37
|
+
[](https://github.com/mrhamidrezam/spanwire/actions/workflows/ci.yml)
|
|
38
|
+
[](https://peps.python.org/pep-0561/)
|
|
39
|
+
[](#)
|
|
40
|
+
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 💡 Why spanwire?
|
|
46
|
+
|
|
47
|
+
Standard distributed tracing solutions (like OpenTelemetry SDK) pull in dozens of heavy transitive dependencies, incur noticeable startup delays, and add architectural bloat to lightweight microservices, CLI utilities, serverless functions (AWS Lambda, Cloud Run), and async LLM pipelines.
|
|
48
|
+
|
|
49
|
+
**spanwire** delivers enterprise-grade distributed telemetry and error context trails with **zero external dependencies**, utilizing pure Python standard library primitives (`contextvars`, `dataclasses`, `time`, `secrets`).
|
|
50
|
+
|
|
51
|
+
- **100% W3C Trace Context & Baggage Standard**: Interoperable with Datadog, Jaeger, Grafana Tempo, AWS X-Ray, and OpenTelemetry collectors.
|
|
52
|
+
- **Flawless Context Propagation**: Works out of the box across `asyncio` coroutines, background tasks, and OS threads without context leakage.
|
|
53
|
+
- **In-Memory Breadcrumb Trail**: Maintains an efficient circular ring-buffer of recent operational milestones, automatically attached when errors or exceptions occur.
|
|
54
|
+
- **Multiple Exporters**: ANSI-colorized CLI console, NDJSON/JSONLines for log aggregators (Vector, Fluentd, Promtail), or in-memory collectors for unit testing.
|
|
55
|
+
- **Deterministic Sampling**: Ratio-based trace hashing guarantees synchronized sampling decisions across distributed microservice boundaries.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 📐 Architecture & Flow
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
sequenceDiagram
|
|
63
|
+
autonumber
|
|
64
|
+
participant Client as API Gateway (Service A)
|
|
65
|
+
participant Wire as W3C HTTP Headers
|
|
66
|
+
participant Worker as Downstream Worker (Service B)
|
|
67
|
+
participant Exporter as Telemetry Exporter
|
|
68
|
+
|
|
69
|
+
Client->>Client: Start Span ("checkout.process")
|
|
70
|
+
Client->>Client: Record Breadcrumb ("User tier: VIP")
|
|
71
|
+
Client->>Wire: Inject W3C traceparent & baggage headers
|
|
72
|
+
Wire->>Worker: Forward HTTP Request
|
|
73
|
+
Worker->>Worker: Extract W3C Context (inherits trace_id)
|
|
74
|
+
Worker->>Worker: Execute child span ("db.persist_order")
|
|
75
|
+
Worker-->>Exporter: Export child span ([OK] 12.4ms)
|
|
76
|
+
Client-->>Exporter: Export parent span ([OK] 45.1ms)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 🚀 Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install spanwire
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
*Requires Python 3.10 or higher. No third-party dependencies required.*
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ⚡ Quick Start
|
|
92
|
+
|
|
93
|
+
### 1. Basic Spans and Console Export
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import time
|
|
97
|
+
from spanwire import Tracer, ConsoleExporter
|
|
98
|
+
|
|
99
|
+
tracer = Tracer(
|
|
100
|
+
service_name="payment-service",
|
|
101
|
+
exporter=ConsoleExporter(colored=True)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
tracer.add_breadcrumb("Validating customer cart", category="cart")
|
|
105
|
+
|
|
106
|
+
with tracer.span("process_payment", attributes={"order.id": "ord_1042", "amount": 89.00}) as span:
|
|
107
|
+
time.sleep(0.02)
|
|
108
|
+
|
|
109
|
+
# Nested child span
|
|
110
|
+
with tracer.span("stripe.charge") as child:
|
|
111
|
+
time.sleep(0.03)
|
|
112
|
+
child.set_attribute("charge.id", "ch_abc123")
|
|
113
|
+
tracer.add_breadcrumb("Payment captured via webhook", category="billing")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Console Output:
|
|
117
|
+
```text
|
|
118
|
+
⚡ [OK] stripe.charge (30.12ms) [trace=63d3791c.. span=ff0dba3fa8e11f3c parent=97bc44600e80142d]
|
|
119
|
+
↳ attributes: service.name='payment-service', charge.id='ch_abc123'
|
|
120
|
+
↳ breadcrumbs (2):
|
|
121
|
+
- [INFO] cart: Validating customer cart
|
|
122
|
+
- [INFO] billing: Payment captured via webhook
|
|
123
|
+
⚡ [OK] process_payment (50.84ms) [trace=63d3791c.. span=97bc44600e80142d]
|
|
124
|
+
↳ attributes: service.name='payment-service', order.id='ord_1042', amount=89.0
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 2. Distributed Tracing over HTTP (W3C Traceparent & Baggage)
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import requests
|
|
133
|
+
from spanwire import Tracer, set_baggage_item
|
|
134
|
+
|
|
135
|
+
tracer = Tracer(service_name="gateway")
|
|
136
|
+
|
|
137
|
+
# Set user context in baggage
|
|
138
|
+
set_baggage_item("user_id", "u_8841")
|
|
139
|
+
|
|
140
|
+
with tracer.span("http_outgoing_call") as span:
|
|
141
|
+
# 1. Inject W3C headers into outbound HTTP request
|
|
142
|
+
headers = {}
|
|
143
|
+
tracer.inject_headers(headers)
|
|
144
|
+
# headers contains:
|
|
145
|
+
# 'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
|
|
146
|
+
# 'baggage': 'user_id=u_8841'
|
|
147
|
+
|
|
148
|
+
# requests.get("https://worker.internal/api/tasks", headers=headers)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
On the receiving downstream service:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from spanwire import Tracer
|
|
155
|
+
|
|
156
|
+
worker_tracer = Tracer(service_name="worker")
|
|
157
|
+
|
|
158
|
+
def handle_incoming_request(incoming_headers: dict):
|
|
159
|
+
# Extract parent context & baggage from headers
|
|
160
|
+
parent_ctx, baggage, _ = worker_tracer.extract_headers(incoming_headers)
|
|
161
|
+
|
|
162
|
+
# Continue the distributed trace seamlessly
|
|
163
|
+
with worker_tracer.span("worker.handle_job", parent_context=parent_ctx) as span:
|
|
164
|
+
print(f"Propagated trace_id: {span.context.trace_id}")
|
|
165
|
+
print(f"Parent span_id: {span.parent_span_id}")
|
|
166
|
+
print(f"User ID from baggage: {baggage.get('user_id')}")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### 3. Asynchronous Function Decoration & LLM Agent Pipeline
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import asyncio
|
|
175
|
+
from spanwire import Tracer, ConsoleExporter
|
|
176
|
+
|
|
177
|
+
tracer = Tracer(service_name="llm-orchestrator", exporter=ConsoleExporter())
|
|
178
|
+
|
|
179
|
+
@tracer.trace(name="llm.generate_plan")
|
|
180
|
+
async def generate_plan(prompt: str) -> list[str]:
|
|
181
|
+
tracer.add_breadcrumb(f"Generating plan for: {prompt[:30]}", category="llm")
|
|
182
|
+
await asyncio.sleep(0.05)
|
|
183
|
+
return ["search_db", "render_markdown"]
|
|
184
|
+
|
|
185
|
+
async def main():
|
|
186
|
+
with tracer.span("orchestrate_query"):
|
|
187
|
+
plan = await generate_plan("Summarize latest server telemetry")
|
|
188
|
+
|
|
189
|
+
asyncio.run(main())
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 🛠️ Exporters
|
|
195
|
+
|
|
196
|
+
| Exporter | Description | Best Suited For |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `ConsoleExporter` | Formatted CLI output with ANSI colors, durations, and breadcrumb trails | Development, debugging, CLI tools |
|
|
199
|
+
| `JsonLinesExporter` | Newline-delimited JSON (NDJSON) output to file or stdout | Vector, Promtail, Datadog Agent, CloudWatch |
|
|
200
|
+
| `InMemoryExporter` | Stores finished spans in a thread-safe list | Pytest unit assertions, custom telemetry queues |
|
|
201
|
+
| `CallbackExporter` | Invokes custom callback function on each completed span | Custom event emission, Webhooks |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 🎯 Samplers
|
|
206
|
+
|
|
207
|
+
- **`AlwaysOnSampler()`**: Traces 100% of operations (default).
|
|
208
|
+
- **`AlwaysOffSampler()`**: Disables tracing overhead completely.
|
|
209
|
+
- **`RatioSampler(ratio=0.1)`**: Probabilistically samples `10%` of traces using deterministic hashing on `trace_id`.
|
|
210
|
+
- **`ParentBasedSampler(root=...)`**: Respects the upstream service's sampling flag, with fallback to root sampler.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 🧪 Running Unit Tests
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
python -m pytest tests -v
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
All 33 unit tests execute in ~0.15s with zero external test plugins.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 📜 License
|
|
225
|
+
|
|
226
|
+
MIT License © 2026 Hamidreza Moghaddam Kohi. See [LICENSE](LICENSE) for details.
|
spanwire-0.1.0/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ spanwire
|
|
4
|
+
|
|
5
|
+
**Zero-dependency micro-tracing, distributed context propagation & breadcrumb telemetry engine for Python.**
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/spanwire/)
|
|
8
|
+
[](https://pypi.org/project/spanwire/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://github.com/mrhamidrezam/spanwire/actions/workflows/ci.yml)
|
|
11
|
+
[](https://peps.python.org/pep-0561/)
|
|
12
|
+
[](#)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 💡 Why spanwire?
|
|
19
|
+
|
|
20
|
+
Standard distributed tracing solutions (like OpenTelemetry SDK) pull in dozens of heavy transitive dependencies, incur noticeable startup delays, and add architectural bloat to lightweight microservices, CLI utilities, serverless functions (AWS Lambda, Cloud Run), and async LLM pipelines.
|
|
21
|
+
|
|
22
|
+
**spanwire** delivers enterprise-grade distributed telemetry and error context trails with **zero external dependencies**, utilizing pure Python standard library primitives (`contextvars`, `dataclasses`, `time`, `secrets`).
|
|
23
|
+
|
|
24
|
+
- **100% W3C Trace Context & Baggage Standard**: Interoperable with Datadog, Jaeger, Grafana Tempo, AWS X-Ray, and OpenTelemetry collectors.
|
|
25
|
+
- **Flawless Context Propagation**: Works out of the box across `asyncio` coroutines, background tasks, and OS threads without context leakage.
|
|
26
|
+
- **In-Memory Breadcrumb Trail**: Maintains an efficient circular ring-buffer of recent operational milestones, automatically attached when errors or exceptions occur.
|
|
27
|
+
- **Multiple Exporters**: ANSI-colorized CLI console, NDJSON/JSONLines for log aggregators (Vector, Fluentd, Promtail), or in-memory collectors for unit testing.
|
|
28
|
+
- **Deterministic Sampling**: Ratio-based trace hashing guarantees synchronized sampling decisions across distributed microservice boundaries.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📐 Architecture & Flow
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
sequenceDiagram
|
|
36
|
+
autonumber
|
|
37
|
+
participant Client as API Gateway (Service A)
|
|
38
|
+
participant Wire as W3C HTTP Headers
|
|
39
|
+
participant Worker as Downstream Worker (Service B)
|
|
40
|
+
participant Exporter as Telemetry Exporter
|
|
41
|
+
|
|
42
|
+
Client->>Client: Start Span ("checkout.process")
|
|
43
|
+
Client->>Client: Record Breadcrumb ("User tier: VIP")
|
|
44
|
+
Client->>Wire: Inject W3C traceparent & baggage headers
|
|
45
|
+
Wire->>Worker: Forward HTTP Request
|
|
46
|
+
Worker->>Worker: Extract W3C Context (inherits trace_id)
|
|
47
|
+
Worker->>Worker: Execute child span ("db.persist_order")
|
|
48
|
+
Worker-->>Exporter: Export child span ([OK] 12.4ms)
|
|
49
|
+
Client-->>Exporter: Export parent span ([OK] 45.1ms)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🚀 Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install spanwire
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
*Requires Python 3.10 or higher. No third-party dependencies required.*
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## ⚡ Quick Start
|
|
65
|
+
|
|
66
|
+
### 1. Basic Spans and Console Export
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import time
|
|
70
|
+
from spanwire import Tracer, ConsoleExporter
|
|
71
|
+
|
|
72
|
+
tracer = Tracer(
|
|
73
|
+
service_name="payment-service",
|
|
74
|
+
exporter=ConsoleExporter(colored=True)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
tracer.add_breadcrumb("Validating customer cart", category="cart")
|
|
78
|
+
|
|
79
|
+
with tracer.span("process_payment", attributes={"order.id": "ord_1042", "amount": 89.00}) as span:
|
|
80
|
+
time.sleep(0.02)
|
|
81
|
+
|
|
82
|
+
# Nested child span
|
|
83
|
+
with tracer.span("stripe.charge") as child:
|
|
84
|
+
time.sleep(0.03)
|
|
85
|
+
child.set_attribute("charge.id", "ch_abc123")
|
|
86
|
+
tracer.add_breadcrumb("Payment captured via webhook", category="billing")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Console Output:
|
|
90
|
+
```text
|
|
91
|
+
⚡ [OK] stripe.charge (30.12ms) [trace=63d3791c.. span=ff0dba3fa8e11f3c parent=97bc44600e80142d]
|
|
92
|
+
↳ attributes: service.name='payment-service', charge.id='ch_abc123'
|
|
93
|
+
↳ breadcrumbs (2):
|
|
94
|
+
- [INFO] cart: Validating customer cart
|
|
95
|
+
- [INFO] billing: Payment captured via webhook
|
|
96
|
+
⚡ [OK] process_payment (50.84ms) [trace=63d3791c.. span=97bc44600e80142d]
|
|
97
|
+
↳ attributes: service.name='payment-service', order.id='ord_1042', amount=89.0
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### 2. Distributed Tracing over HTTP (W3C Traceparent & Baggage)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import requests
|
|
106
|
+
from spanwire import Tracer, set_baggage_item
|
|
107
|
+
|
|
108
|
+
tracer = Tracer(service_name="gateway")
|
|
109
|
+
|
|
110
|
+
# Set user context in baggage
|
|
111
|
+
set_baggage_item("user_id", "u_8841")
|
|
112
|
+
|
|
113
|
+
with tracer.span("http_outgoing_call") as span:
|
|
114
|
+
# 1. Inject W3C headers into outbound HTTP request
|
|
115
|
+
headers = {}
|
|
116
|
+
tracer.inject_headers(headers)
|
|
117
|
+
# headers contains:
|
|
118
|
+
# 'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
|
|
119
|
+
# 'baggage': 'user_id=u_8841'
|
|
120
|
+
|
|
121
|
+
# requests.get("https://worker.internal/api/tasks", headers=headers)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
On the receiving downstream service:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from spanwire import Tracer
|
|
128
|
+
|
|
129
|
+
worker_tracer = Tracer(service_name="worker")
|
|
130
|
+
|
|
131
|
+
def handle_incoming_request(incoming_headers: dict):
|
|
132
|
+
# Extract parent context & baggage from headers
|
|
133
|
+
parent_ctx, baggage, _ = worker_tracer.extract_headers(incoming_headers)
|
|
134
|
+
|
|
135
|
+
# Continue the distributed trace seamlessly
|
|
136
|
+
with worker_tracer.span("worker.handle_job", parent_context=parent_ctx) as span:
|
|
137
|
+
print(f"Propagated trace_id: {span.context.trace_id}")
|
|
138
|
+
print(f"Parent span_id: {span.parent_span_id}")
|
|
139
|
+
print(f"User ID from baggage: {baggage.get('user_id')}")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### 3. Asynchronous Function Decoration & LLM Agent Pipeline
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import asyncio
|
|
148
|
+
from spanwire import Tracer, ConsoleExporter
|
|
149
|
+
|
|
150
|
+
tracer = Tracer(service_name="llm-orchestrator", exporter=ConsoleExporter())
|
|
151
|
+
|
|
152
|
+
@tracer.trace(name="llm.generate_plan")
|
|
153
|
+
async def generate_plan(prompt: str) -> list[str]:
|
|
154
|
+
tracer.add_breadcrumb(f"Generating plan for: {prompt[:30]}", category="llm")
|
|
155
|
+
await asyncio.sleep(0.05)
|
|
156
|
+
return ["search_db", "render_markdown"]
|
|
157
|
+
|
|
158
|
+
async def main():
|
|
159
|
+
with tracer.span("orchestrate_query"):
|
|
160
|
+
plan = await generate_plan("Summarize latest server telemetry")
|
|
161
|
+
|
|
162
|
+
asyncio.run(main())
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 🛠️ Exporters
|
|
168
|
+
|
|
169
|
+
| Exporter | Description | Best Suited For |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `ConsoleExporter` | Formatted CLI output with ANSI colors, durations, and breadcrumb trails | Development, debugging, CLI tools |
|
|
172
|
+
| `JsonLinesExporter` | Newline-delimited JSON (NDJSON) output to file or stdout | Vector, Promtail, Datadog Agent, CloudWatch |
|
|
173
|
+
| `InMemoryExporter` | Stores finished spans in a thread-safe list | Pytest unit assertions, custom telemetry queues |
|
|
174
|
+
| `CallbackExporter` | Invokes custom callback function on each completed span | Custom event emission, Webhooks |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 🎯 Samplers
|
|
179
|
+
|
|
180
|
+
- **`AlwaysOnSampler()`**: Traces 100% of operations (default).
|
|
181
|
+
- **`AlwaysOffSampler()`**: Disables tracing overhead completely.
|
|
182
|
+
- **`RatioSampler(ratio=0.1)`**: Probabilistically samples `10%` of traces using deterministic hashing on `trace_id`.
|
|
183
|
+
- **`ParentBasedSampler(root=...)`**: Respects the upstream service's sampling flag, with fallback to root sampler.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🧪 Running Unit Tests
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
python -m pytest tests -v
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
All 33 unit tests execute in ~0.15s with zero external test plugins.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 📜 License
|
|
198
|
+
|
|
199
|
+
MIT License © 2026 Hamidreza Moghaddam Kohi. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spanwire"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Zero-dependency micro-tracing, distributed context propagation & breadcrumb telemetry engine for Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Hamidreza", email = "h4midrezam@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"tracing",
|
|
17
|
+
"distributed-tracing",
|
|
18
|
+
"opentelemetry",
|
|
19
|
+
"w3c",
|
|
20
|
+
"traceparent",
|
|
21
|
+
"baggage",
|
|
22
|
+
"breadcrumbs",
|
|
23
|
+
"telemetry",
|
|
24
|
+
"contextvars",
|
|
25
|
+
"zero-dependency",
|
|
26
|
+
"async"
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Programming Language :: Python :: 3.14",
|
|
37
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
38
|
+
"Topic :: System :: Monitoring",
|
|
39
|
+
"Topic :: System :: Logging",
|
|
40
|
+
"Typing :: Typed",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/mrhamidrezam/spanwire"
|
|
45
|
+
Repository = "https://github.com/mrhamidrezam/spanwire"
|
|
46
|
+
Issues = "https://github.com/mrhamidrezam/spanwire/issues"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
where = ["src"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-data]
|
|
52
|
+
spanwire = ["py.typed"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
pythonpath = ["src"]
|
|
56
|
+
testpaths = ["tests"]
|
spanwire-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""spanwire: Zero-dependency micro-tracing, distributed context propagation & breadcrumb telemetry engine for Python."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Dict, Mapping, Optional
|
|
6
|
+
|
|
7
|
+
from spanwire._version import __version__
|
|
8
|
+
from spanwire.breadcrumbs import Breadcrumb, BreadcrumbBuffer
|
|
9
|
+
from spanwire.context import (
|
|
10
|
+
clear_baggage,
|
|
11
|
+
get_baggage,
|
|
12
|
+
get_baggage_item,
|
|
13
|
+
get_current_span,
|
|
14
|
+
set_baggage,
|
|
15
|
+
set_baggage_item,
|
|
16
|
+
)
|
|
17
|
+
from spanwire.core import (
|
|
18
|
+
Span,
|
|
19
|
+
SpanContext,
|
|
20
|
+
SpanEvent,
|
|
21
|
+
SpanStatus,
|
|
22
|
+
Tracer,
|
|
23
|
+
generate_span_id,
|
|
24
|
+
generate_trace_id,
|
|
25
|
+
)
|
|
26
|
+
from spanwire.exporters import (
|
|
27
|
+
CallbackExporter,
|
|
28
|
+
ConsoleExporter,
|
|
29
|
+
InMemoryExporter,
|
|
30
|
+
JsonLinesExporter,
|
|
31
|
+
SpanExporter,
|
|
32
|
+
)
|
|
33
|
+
from spanwire.sampler import (
|
|
34
|
+
AlwaysOffSampler,
|
|
35
|
+
AlwaysOnSampler,
|
|
36
|
+
ParentBasedSampler,
|
|
37
|
+
RatioSampler,
|
|
38
|
+
Sampler,
|
|
39
|
+
)
|
|
40
|
+
from spanwire.w3c import (
|
|
41
|
+
extract_w3c_headers,
|
|
42
|
+
format_baggage,
|
|
43
|
+
format_traceparent,
|
|
44
|
+
inject_w3c_headers,
|
|
45
|
+
is_valid_span_id,
|
|
46
|
+
is_valid_trace_id,
|
|
47
|
+
parse_baggage,
|
|
48
|
+
parse_traceparent,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
_DEFAULT_TRACER: Optional[Tracer] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_tracer(service_name: str = "default-service") -> Tracer:
|
|
55
|
+
"""Get or initialize the global default Tracer."""
|
|
56
|
+
global _DEFAULT_TRACER
|
|
57
|
+
if _DEFAULT_TRACER is None:
|
|
58
|
+
_DEFAULT_TRACER = Tracer(service_name=service_name)
|
|
59
|
+
return _DEFAULT_TRACER
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def set_tracer(tracer: Tracer) -> None:
|
|
63
|
+
"""Set the global default Tracer."""
|
|
64
|
+
global _DEFAULT_TRACER
|
|
65
|
+
_DEFAULT_TRACER = tracer
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def span(
|
|
69
|
+
name: str,
|
|
70
|
+
attributes: Optional[Mapping[str, Any]] = None,
|
|
71
|
+
parent_context: Optional[SpanContext] = None,
|
|
72
|
+
) -> Span:
|
|
73
|
+
"""Create a span on the default global tracer."""
|
|
74
|
+
return get_tracer().span(name, attributes=attributes, parent_context=parent_context)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def trace(
|
|
78
|
+
name: Optional[str] = None,
|
|
79
|
+
attributes: Optional[Mapping[str, Any]] = None,
|
|
80
|
+
) -> Callable[..., Any]:
|
|
81
|
+
"""Decorator to trace a function using the default global tracer."""
|
|
82
|
+
return get_tracer().trace(name=name, attributes=attributes)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def add_breadcrumb(
|
|
86
|
+
message: str,
|
|
87
|
+
level: str = "INFO",
|
|
88
|
+
category: str = "default",
|
|
89
|
+
data: Optional[Dict[str, Any]] = None,
|
|
90
|
+
**kwargs: Any,
|
|
91
|
+
) -> Breadcrumb:
|
|
92
|
+
"""Add a breadcrumb to the default global tracer."""
|
|
93
|
+
return get_tracer().add_breadcrumb(
|
|
94
|
+
message=message, level=level, category=category, data=data, **kwargs
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
__all__ = [
|
|
99
|
+
"__version__",
|
|
100
|
+
# Core
|
|
101
|
+
"Tracer",
|
|
102
|
+
"Span",
|
|
103
|
+
"SpanContext",
|
|
104
|
+
"SpanEvent",
|
|
105
|
+
"SpanStatus",
|
|
106
|
+
"generate_trace_id",
|
|
107
|
+
"generate_span_id",
|
|
108
|
+
# Global conveniences
|
|
109
|
+
"get_tracer",
|
|
110
|
+
"set_tracer",
|
|
111
|
+
"span",
|
|
112
|
+
"trace",
|
|
113
|
+
"add_breadcrumb",
|
|
114
|
+
# Context
|
|
115
|
+
"get_current_span",
|
|
116
|
+
"get_baggage",
|
|
117
|
+
"set_baggage",
|
|
118
|
+
"set_baggage_item",
|
|
119
|
+
"get_baggage_item",
|
|
120
|
+
"clear_baggage",
|
|
121
|
+
# Breadcrumbs
|
|
122
|
+
"Breadcrumb",
|
|
123
|
+
"BreadcrumbBuffer",
|
|
124
|
+
# Samplers
|
|
125
|
+
"Sampler",
|
|
126
|
+
"AlwaysOnSampler",
|
|
127
|
+
"AlwaysOffSampler",
|
|
128
|
+
"RatioSampler",
|
|
129
|
+
"ParentBasedSampler",
|
|
130
|
+
# Exporters
|
|
131
|
+
"SpanExporter",
|
|
132
|
+
"InMemoryExporter",
|
|
133
|
+
"JsonLinesExporter",
|
|
134
|
+
"ConsoleExporter",
|
|
135
|
+
"CallbackExporter",
|
|
136
|
+
# W3C
|
|
137
|
+
"format_traceparent",
|
|
138
|
+
"parse_traceparent",
|
|
139
|
+
"format_baggage",
|
|
140
|
+
"parse_baggage",
|
|
141
|
+
"inject_w3c_headers",
|
|
142
|
+
"extract_w3c_headers",
|
|
143
|
+
"is_valid_trace_id",
|
|
144
|
+
"is_valid_span_id",
|
|
145
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|