diff options
Diffstat (limited to 'crates/yt_dlp/src/logging.rs')
-rw-r--r-- | crates/yt_dlp/src/logging.rs | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/crates/yt_dlp/src/logging.rs b/crates/yt_dlp/src/logging.rs new file mode 100644 index 0000000..cca917c --- /dev/null +++ b/crates/yt_dlp/src/logging.rs @@ -0,0 +1,125 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +// This file is taken from: https://github.com/dylanbstorey/pyo3-pylogger/blob/d89e0d6820ebc4f067647e3b74af59dbc4941dd5/src/lib.rs +// It is licensed under the Apache 2.0 License, copyright up to 2024 by Dylan Storey +// It was modified by Benedikt Peetz 2024 + +use log::{logger, Level, MetadataBuilder, Record}; +use pyo3::{ + prelude::{PyAnyMethods, PyListMethods, PyModuleMethods}, + pyfunction, wrap_pyfunction, Bound, PyAny, PyResult, Python, +}; + +/// Consume a Python `logging.LogRecord` and emit a Rust `Log` instead. +#[pyfunction] +fn host_log<'a>(record: Bound<'a, PyAny>, rust_target: &str) -> PyResult<()> { + let level = record.getattr("levelno")?; + let message = record.getattr("getMessage")?.call0()?.to_string(); + let pathname = record.getattr("pathname")?.to_string(); + let lineno = record + .getattr("lineno")? + .to_string() + .parse::<u32>() + .expect("This should always be a u32"); + + let logger_name = record.getattr("name")?.to_string(); + + let full_target: Option<String> = if logger_name.trim().is_empty() || logger_name == "root" { + None + } else { + // Libraries (ex: tracing_subscriber::filter::Directive) expect rust-style targets like foo::bar, + // and may not deal well with "." as a module separator: + let logger_name = logger_name.replace(".", "::"); + Some(format!("{rust_target}::{logger_name}")) + }; + + let target = full_target + .as_ref() + .map(|x| x.as_str()) + .unwrap_or(rust_target); + + // error + let error_metadata = if level.ge(40u8)? { + MetadataBuilder::new() + .target(target) + .level(Level::Error) + .build() + } else if level.ge(30u8)? { + MetadataBuilder::new() + .target(target) + .level(Level::Warn) + .build() + } else if level.ge(20u8)? { + MetadataBuilder::new() + .target(target) + .level(Level::Info) + .build() + } else if level.ge(10u8)? { + MetadataBuilder::new() + .target(target) + .level(Level::Debug) + .build() + } else { + MetadataBuilder::new() + .target(target) + .level(Level::Trace) + .build() + }; + + logger().log( + &Record::builder() + .metadata(error_metadata) + .args(format_args!("{}", &message)) + .line(Some(lineno)) + .file(None) + .module_path(Some(&pathname)) + .build(), + ); + + Ok(()) +} + +/// Registers the host_log function in rust as the event handler for Python's logging logger +/// This function needs to be called from within a pyo3 context as early as possible to ensure logging messages +/// arrive to the rust consumer. +pub fn setup_logging(py: Python, target: &str) -> PyResult<()> { + let logging = py.import_bound("logging")?; + + logging.setattr("host_log", wrap_pyfunction!(host_log, &logging)?)?; + + py.run_bound( + format!( + r#" +class HostHandler(Handler): + def __init__(self, level=0): + super().__init__(level=level) + + def emit(self, record): + host_log(record,"{}") + +oldBasicConfig = basicConfig +def basicConfig(*pargs, **kwargs): + if "handlers" not in kwargs: + kwargs["handlers"] = [HostHandler()] + return oldBasicConfig(*pargs, **kwargs) +"#, + target + ) + .as_str(), + Some(&logging.dict()), + None, + )?; + + let all = logging.index()?; + all.append("HostHandler")?; + + Ok(()) +} |