Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add ecosystems/tracing.md to guide #4713

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- [Supporting multiple Python versions](building-and-distribution/multiple-python-versions.md)
- [Useful crates](ecosystem.md)
- [Logging](ecosystem/logging.md)
- [Tracing](ecosystem/tracing.md)
- [Using `async` and `await`](ecosystem/async-await.md)
- [FAQ and troubleshooting](faq.md)

Expand Down
107 changes: 107 additions & 0 deletions guide/src/ecosystem/tracing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Tracing

Python projects that write extension modules for performance reasons may want to
tap into [Rust's `tracing` ecosystem] to gain insight into the performance of
their extension module.

This section of the guide describes a few crates that provide ways to do that.
They build on [`tracing_subscriber`][tracing-subscriber] and require code
changes in both Python and Rust to integrate. Note that each extension module
must configure its own `tracing` integration; one extension module will not see
`tracing` data from a different module.

## `pyo3-tracing-subscriber` ([documentation][pyo3-tracing-subscriber-docs])

[`pyo3-tracing-subscriber`][pyo3-tracing-subscriber] provides a way for Python
projects to configure `tracing_subscriber`. It exposes a few
`tracing_subscriber` layers:
- `tracing_subscriber::fmt` for writing human-readable output to file or stdout
- `opentelemetry-stdout` for writing OTLP output to file or stdout
- `opentelemetry-otlp` for writing OTLP output to an OTLP endpoint

The extension module must call [`pyo3_tracing_subscriber::add_submodule`][add-submodule]
to export the Python classes needed to configure and initialize `tracing`.

On the Python side, use the `Tracing` context manager to initialize tracing and
run Rust code inside the context manager's block. `Tracing` takes a
`GlobalTracingConfig` instance describing the layers to be used.

See [the README on crates.io][pyo3-tracing-subscriber]
for example code.

## `pyo3-python-tracing-subscriber` ([documentation][pyo3-python-tracing-subscriber-docs])

The similarly-named [`pyo3-python-tracing-subscriber`][pyo3-python-tracing-subscriber]
implements a shim in Rust that forwards `tracing` data to a `Layer`
implementation defined in and passed in from Python.

There are many ways an extension module could integrate `pyo3-python-tracing-subscriber`
but a simple one may look something like this:
```rust
#[tracing::instrument]
#[pyfunction]
fn fibonacci(index: usize, use_memoized: bool) -> PyResult<usize> {
// ...
}

#[pyfunction]
pub fn initialize_tracing(py_impl: Bound<'_, PyAny>) {
tracing_subscriber::registry()
.with(pyo3_python_tracing_subscriber::PythonCallbackLayerBridge::new(py_impl))
.init();
}
```
The extension module must provide some way for Python to pass in one or more
Python objects that implement [the `Layer` interface]. Then it should construct
[`pyo3_python_tracing_subscriber::PythonCallbackLayerBridge`][PythonCallbackLayerBridge]
instances with each of those Python objects and initialize `tracing_subscriber`
as shown above.

The Python objects implement a modified version of the `Layer` interface:
- `on_new_span()` may return some state that will stored inside the Rust span
- other callbacks will be given that state as an additional positional argument

A dummy `Layer` implementation may look like this:
```python
import rust_extension

class MyPythonLayer:
def __init__(self):
pass

# `on_new_span` can return some state
def on_new_span(self, span_attrs: str, span_id: str) -> int:
print(f"[on_new_span]: {span_attrs} | {span_id}")
return random.randint(1, 1000)

# The state from `on_new_span` is passed back into other trait methods
def on_event(self, event: str, state: int):
print(f"[on_event]: {event} | {state}")

def on_close(self, span_id: str, state: int):
print(f"[on_close]: {span_id} | {state}")

def on_record(self, span_id: str, values: str, state: int):
print(f"[on_record]: {span_id} | {values} | {state}")

def main():
rust_extension.initialize_tracing(MyPythonLayer())

print("10th fibonacci number: ", rust_extension.fibonacci(10, True))
```

`pyo3-python-tracing-subscriber` has [working examples]
showing both the Rust side and the Python side of an integration.

[pyo3-tracing-subscriber]: https://crates.io/crates/pyo3-tracing-subscriber
[pyo3-tracing-subscriber-docs]: https://docs.rs/pyo3-tracing-subscriber
[add-submodule]: https://docs.rs/pyo3-tracing-subscriber/*/pyo3_tracing_subscriber/fn.add_submodule.html

[pyo3-python-tracing-subscriber]: https://crates.io/crates/pyo3-python-tracing-subscriber
[pyo3-python-tracing-subscriber-docs]: https://docs.rs/pyo3-python-tracing-subscriber
[PythonCallbackLayerBridge]: https://docs.rs/pyo3-python-tracing-subscriber/*/pyo3_python_tracing_subscriber/struct.PythonCallbackLayerBridge.html
[working examples]: https://github.com/getsentry/pyo3-python-tracing-subscriber/tree/main/demo

[Rust's `tracing` ecosystem]: https://crates.io/crates/tracing
[tracing-subscriber]: https://docs.rs/tracing-subscriber/*/tracing_subscriber/
[the `Layer` interface]: https://docs.rs/tracing-subscriber/*/tracing_subscriber/layer/trait.Layer.html
Loading