about summary refs log tree commit diff stats
path: root/crates/yt_dlp/src/logging.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 13:06:00 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 13:06:00 +0200
commit52e99b38eae6b4f3cb991342ff9ba9abbab9e42c (patch)
tree9fa6f0582dfb8b6dc7b49bbd6206ab4b533ff900 /crates/yt_dlp/src/logging.rs
parentrefactor(cli): Replace the byte parser with the one from the `bytes` crate (diff)
downloadyt-52e99b38eae6b4f3cb991342ff9ba9abbab9e42c.tar.gz
yt-52e99b38eae6b4f3cb991342ff9ba9abbab9e42c.zip
refactor(yt_dlp): Also move the `crates` subdirectory
Diffstat (limited to 'crates/yt_dlp/src/logging.rs')
-rw-r--r--crates/yt_dlp/src/logging.rs125
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(())
+}