pyo3_tracing_subscriber/
lib.rs

1// Copyright 2023 Rigetti Computing
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Covers correctness, suspicious, style, complexity, and perf
16#![deny(clippy::all)]
17#![deny(clippy::pedantic)]
18#![deny(clippy::cargo)]
19#![warn(clippy::nursery)]
20// Has false positives that conflict with unreachable_pub
21#![allow(clippy::redundant_pub_crate)]
22#![deny(
23    absolute_paths_not_starting_with_crate,
24    anonymous_parameters,
25    bad_style,
26    dead_code,
27    keyword_idents,
28    improper_ctypes,
29    macro_use_extern_crate,
30    meta_variable_misuse, // May have false positives
31    missing_abi,
32    missing_debug_implementations, // can affect compile time/code size
33    missing_docs,
34    no_mangle_generic_items,
35    non_shorthand_field_patterns,
36    noop_method_call,
37    overflowing_literals,
38    path_statements,
39    patterns_in_fns_without_body,
40    private_interfaces,
41    private_bounds,
42    semicolon_in_expressions_from_macros,
43    trivial_casts,
44    trivial_numeric_casts,
45    unconditional_recursion,
46    unreachable_pub,
47    unsafe_code,
48    unused,
49    unused_allocation,
50    unused_comparisons,
51    unused_extern_crates,
52    unused_import_braces,
53    unused_lifetimes,
54    unused_parens,
55    variant_size_differences,
56    while_true
57)]
58//! This crate provides utilities for configuring and initializing a tracing subscriber from
59//! Python. Because Rust pyo3-based Python packages are binaries, these utilities are exposed
60//! as a `pyo3::types::PyModule` which can then be added to upstream pyo3 libraries.
61//!
62//! # Features
63//!
64//! * `pyo3` - enables the Python bindings for the tracing subscriber. This feature is enabled by default.
65//! * `extension-module` - enables the Python extension module for the tracing subscriber. This feature is enabled by default.
66//! * `layer-otel-otlp-file` - exports trace data with `opentelemetry-stdout`. See `crate::layers::otel_otlp_file`.
67//! * `layer-otel-otlp` - exports trace data with `opentelemetry-otlp`. See `crate::layers::otel_otlp`.
68//! * `stubs` - supports writing stub files in your Python source code from your Rust build scripts. See `crates::stubs`. This should only be used in build scripts with default features disabled.
69//!
70//! # Requirements and Limitations
71//!
72//! * The tracing subscribers initialized and configured _only_ capture tracing data for the pyo3
73//!   library which adds the `pyo3-tracing-subscriber` module. Separate Python libraries require separate
74//!   bootstrapping.
75//! * Python users can initialize tracing subscribers using context managers either globally, in
76//!   which case they can only initialize once, or per-thread, which is incompatible with Python
77//!   `async/await`.
78//! * The `OTel` OTLP layer requires a heuristic based timeout upon context manager exit to ensure
79//!   trace data on the Rust side is flushed to the OTLP collector. This issue currently persists despite calls
80//!   to `force_flush` on the `opentelemetry_sdk::trace::TracerProvider` and `opentelemetry::global::shutdown_tracer_provider`.
81//!
82//! # Examples
83//!
84//! ```
85//! use pyo3::prelude::*;
86//! use tracing::instrument;
87//!
88//! const MY_PACKAGE_NAME: &str = "example";
89//! const TRACING_SUBSCRIBER_SUBMODULE_NAME: &str = "tracing_subscriber";
90//!
91//! #[pymodule]
92//! fn example(py: Python, m: &PyModule) -> PyResult<()> {
93//!     // add your functions, modules, and classes
94//!     pyo3_tracing_subscriber::add_submodule(
95//!         MY_PACKAGE_NAME,
96//!         TRACING_SUBSCRIBER_SUBMODULE_NAME,
97//!         py,
98//!         m,
99//!     )?;
100//!     Ok(())
101//! }
102//! ```
103//!
104//! Then in Python:
105//!
106//! ```python
107//! import asyncio
108//! from example.tracing_subscriber import Tracing
109//!
110//!
111//! async main():
112//!     async with Tracing():
113//!      
114//!      # do stuff
115//!         pass
116//!
117//!
118//! if __name__ == "__main__":
119//!     asyncio.run(main())
120//! ```
121//!
122//! # Related Crates
123//!
124//! * `pyo3-opentelemetry` - propagates `OpenTelemetry` contexts from Python into Rust.
125#[cfg(feature = "pyo3")]
126use pyo3::{types::PyModule, PyResult, Python};
127
128#[cfg(feature = "pyo3")]
129use self::{
130    contextmanager::{CurrentThreadTracingConfig, GlobalTracingConfig, TracingContextManagerError},
131    export_process::{BatchConfig, SimpleConfig, TracingShutdownError, TracingStartError},
132};
133#[cfg(feature = "pyo3")]
134pub use contextmanager::Tracing;
135
136#[cfg(feature = "pyo3")]
137pub(crate) mod common;
138#[cfg(feature = "pyo3")]
139mod contextmanager;
140#[cfg(feature = "pyo3")]
141mod export_process;
142#[cfg(feature = "pyo3")]
143pub(crate) mod layers;
144#[cfg(feature = "stubs")]
145pub mod stubs;
146#[cfg(feature = "pyo3")]
147pub(crate) mod subscriber;
148
149#[cfg(feature = "pyo3")]
150create_init_submodule! {
151    classes: [
152        Tracing,
153        GlobalTracingConfig,
154        CurrentThreadTracingConfig,
155        BatchConfig,
156        SimpleConfig
157    ],
158    errors: [TracingContextManagerError, TracingStartError, TracingShutdownError],
159    submodules: [
160        "layers": layers::init_submodule,
161        "subscriber": subscriber::init_submodule,
162        "common": common::init_submodule
163    ],
164}
165
166#[cfg(feature = "pyo3")]
167/// Add the tracing submodule to the given module. This will add the submodule to the `sys.modules`
168/// dictionary so that it can be imported from Python.
169///
170/// # Arguments
171///
172/// * `fully_qualified_namespace` - the fully qualified namespace of the parent Python module to
173///   which the tracing submodule should be added. This may be a nested namespace, such as
174///   `my_package.my_module`.
175/// * `name` - the name of the tracing subscriber submodule within the specified parent module.
176///   This should not be a nested namespace.
177/// * `py` - the Python GIL token.
178/// * `parent_module` - the parent module to which the tracing subscriber submodule should be added.
179///
180/// # Errors
181///
182/// * `PyErr` if the submodule cannot be added.
183///
184/// # Additional Details
185///
186/// This function will add the following:
187///
188/// * `Tracing` - a Python context manager which initializes the configured tracing subscriber.
189/// * `GlobalTracingConfig` - a Python context manager which sets the configured tracing subscriber
190///   as the global default (ie `tracing::subscriber::set_global_default`). The `Tracing` context
191///   manager can be used _only once_ per process with this configuration.
192/// * `CurrentThreadTracingConfig` - a Python context manager which sets the configured tracing
193///   subscriber as the current thread default (ie `tracing::subscriber::set_default`). As the
194///   context manager exits, the guard is dropped and the tracing subscriber can be re-initialized
195///   with another default. Note, the default tracing subscriber will _not_ capture traces across
196///   `async/await` boundaries that call `pyo3_asyncio::tokio::future_into_py`.
197/// * `BatchConfig` - a Python context manager which configures the tracing subscriber to export
198///   trace data in batch. As the `Tracing` context manager enters, a Tokio runtime is initialized
199///   and will run in the background until the context manager exits.
200/// * `SimpleConfig` - a Python context manager which configures the tracing subscriber to export
201///   trace data in a non-batch manner. This only initializes a Tokio runtime if the underlying layer
202///   requires an asynchronous runtime to export trace data (ie the `opentelemetry-otlp` layer).
203/// * `layers` - a submodule which contains different layers to add to the tracing subscriber.
204///   Currently supported:
205///     * `tracing::fmt` - a layer which exports trace data to stdout in a non-OpenTelemetry data format.
206///     * `opentelemetry-stdout` - a layer which exports trace data to stdout (requires the `layer-otel-otlp-file` feature).
207///     * `opentelemetry-otlp` - a layer which exports trace data to an `OpenTelemetry` collector (requires the `layer-otel-otlp` feature).
208/// * `subscriber` - a submodule which contains utilities for initialing the tracing subscriber
209///   with the configured layer. Currently, the tracing subscriber is initialized as
210///   `tracing::subscriber::Registry::default().with(layer)`.
211///
212/// The following exceptions are added to the submodule:
213///
214/// * `TracingContextManagerError` - raised when the `Tracing` context manager's methods are not
215///   invoked in the correct order or multiplicity.
216/// * `TracingStartError` - raised if the user-specified tracing layer or subscriber fails to build
217///   and initialize properly upon context manager entry.
218/// * `TracingShutdownError` - raised if the tracing layer or subscriber fails to shutdown properly on context manager exit.
219///
220/// For detailed Python usage documentation, see the stub files written by
221/// [`pyo3_tracing_subscriber::stubs::write_stub_files`].
222pub fn add_submodule(
223    fully_qualified_namespace: &str,
224    name: &str,
225    py: Python,
226    parent_module: &PyModule,
227) -> PyResult<()> {
228    let tracing_subscriber = PyModule::new(py, name)?;
229    let fully_qualified_name = format!("{fully_qualified_namespace}.{name}");
230    init_submodule(&fully_qualified_name, py, tracing_subscriber)?;
231    let modules = py.import("sys")?.getattr("modules")?;
232    modules.set_item(fully_qualified_name, tracing_subscriber)?;
233    parent_module.add_submodule(tracing_subscriber)?;
234    Ok(())
235}