Skip to content

Commit b9bab5d

Browse files
authored
docs: add ecosystems/tracing.md to guide (#4713)
1 parent 21132a8 commit b9bab5d

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
- [Supporting multiple Python versions](building-and-distribution/multiple-python-versions.md)
3636
- [Useful crates](ecosystem.md)
3737
- [Logging](ecosystem/logging.md)
38+
- [Tracing](ecosystem/tracing.md)
3839
- [Using `async` and `await`](ecosystem/async-await.md)
3940
- [FAQ and troubleshooting](faq.md)
4041

guide/src/ecosystem/tracing.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Tracing
2+
3+
Python projects that write extension modules for performance reasons may want to
4+
tap into [Rust's `tracing` ecosystem] to gain insight into the performance of
5+
their extension module.
6+
7+
This section of the guide describes a few crates that provide ways to do that.
8+
They build on [`tracing_subscriber`][tracing-subscriber] and require code
9+
changes in both Python and Rust to integrate. Note that each extension module
10+
must configure its own `tracing` integration; one extension module will not see
11+
`tracing` data from a different module.
12+
13+
## `pyo3-tracing-subscriber` ([documentation][pyo3-tracing-subscriber-docs])
14+
15+
[`pyo3-tracing-subscriber`][pyo3-tracing-subscriber] provides a way for Python
16+
projects to configure `tracing_subscriber`. It exposes a few
17+
`tracing_subscriber` layers:
18+
- `tracing_subscriber::fmt` for writing human-readable output to file or stdout
19+
- `opentelemetry-stdout` for writing OTLP output to file or stdout
20+
- `opentelemetry-otlp` for writing OTLP output to an OTLP endpoint
21+
22+
The extension module must call [`pyo3_tracing_subscriber::add_submodule`][add-submodule]
23+
to export the Python classes needed to configure and initialize `tracing`.
24+
25+
On the Python side, use the `Tracing` context manager to initialize tracing and
26+
run Rust code inside the context manager's block. `Tracing` takes a
27+
`GlobalTracingConfig` instance describing the layers to be used.
28+
29+
See [the README on crates.io][pyo3-tracing-subscriber]
30+
for example code.
31+
32+
## `pyo3-python-tracing-subscriber` ([documentation][pyo3-python-tracing-subscriber-docs])
33+
34+
The similarly-named [`pyo3-python-tracing-subscriber`][pyo3-python-tracing-subscriber]
35+
implements a shim in Rust that forwards `tracing` data to a `Layer`
36+
implementation defined in and passed in from Python.
37+
38+
There are many ways an extension module could integrate `pyo3-python-tracing-subscriber`
39+
but a simple one may look something like this:
40+
```rust
41+
#[tracing::instrument]
42+
#[pyfunction]
43+
fn fibonacci(index: usize, use_memoized: bool) -> PyResult<usize> {
44+
// ...
45+
}
46+
47+
#[pyfunction]
48+
pub fn initialize_tracing(py_impl: Bound<'_, PyAny>) {
49+
tracing_subscriber::registry()
50+
.with(pyo3_python_tracing_subscriber::PythonCallbackLayerBridge::new(py_impl))
51+
.init();
52+
}
53+
```
54+
The extension module must provide some way for Python to pass in one or more
55+
Python objects that implement [the `Layer` interface]. Then it should construct
56+
[`pyo3_python_tracing_subscriber::PythonCallbackLayerBridge`][PythonCallbackLayerBridge]
57+
instances with each of those Python objects and initialize `tracing_subscriber`
58+
as shown above.
59+
60+
The Python objects implement a modified version of the `Layer` interface:
61+
- `on_new_span()` may return some state that will stored inside the Rust span
62+
- other callbacks will be given that state as an additional positional argument
63+
64+
A dummy `Layer` implementation may look like this:
65+
```python
66+
import rust_extension
67+
68+
class MyPythonLayer:
69+
def __init__(self):
70+
pass
71+
72+
# `on_new_span` can return some state
73+
def on_new_span(self, span_attrs: str, span_id: str) -> int:
74+
print(f"[on_new_span]: {span_attrs} | {span_id}")
75+
return random.randint(1, 1000)
76+
77+
# The state from `on_new_span` is passed back into other trait methods
78+
def on_event(self, event: str, state: int):
79+
print(f"[on_event]: {event} | {state}")
80+
81+
def on_close(self, span_id: str, state: int):
82+
print(f"[on_close]: {span_id} | {state}")
83+
84+
def on_record(self, span_id: str, values: str, state: int):
85+
print(f"[on_record]: {span_id} | {values} | {state}")
86+
87+
def main():
88+
rust_extension.initialize_tracing(MyPythonLayer())
89+
90+
print("10th fibonacci number: ", rust_extension.fibonacci(10, True))
91+
```
92+
93+
`pyo3-python-tracing-subscriber` has [working examples]
94+
showing both the Rust side and the Python side of an integration.
95+
96+
[pyo3-tracing-subscriber]: https://crates.io/crates/pyo3-tracing-subscriber
97+
[pyo3-tracing-subscriber-docs]: https://docs.rs/pyo3-tracing-subscriber
98+
[add-submodule]: https://docs.rs/pyo3-tracing-subscriber/*/pyo3_tracing_subscriber/fn.add_submodule.html
99+
100+
[pyo3-python-tracing-subscriber]: https://crates.io/crates/pyo3-python-tracing-subscriber
101+
[pyo3-python-tracing-subscriber-docs]: https://docs.rs/pyo3-python-tracing-subscriber
102+
[PythonCallbackLayerBridge]: https://docs.rs/pyo3-python-tracing-subscriber/*/pyo3_python_tracing_subscriber/struct.PythonCallbackLayerBridge.html
103+
[working examples]: https://github.com/getsentry/pyo3-python-tracing-subscriber/tree/main/demo
104+
105+
[Rust's `tracing` ecosystem]: https://crates.io/crates/tracing
106+
[tracing-subscriber]: https://docs.rs/tracing-subscriber/*/tracing_subscriber/
107+
[the `Layer` interface]: https://docs.rs/tracing-subscriber/*/tracing_subscriber/layer/trait.Layer.html

0 commit comments

Comments
 (0)