about summary refs log blame commit diff stats
path: root/crates/yt_dlp/src/logging.rs
blob: e7315020d50f939df07d7638b4f81aeb35859121 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14












                                                                                                                                  

                                                                                               
                      
                                                  
           
                                   
                                                            
                                

                                                                       
                                        
             
                                                                          














                                                                                                         
                                                         

                                                     
                                                               








































                                               
                                                                                              
                                                                                                               

                                     
                                                                    
                                        

                                                                        
                             




                                     
                                   




                                            
  

                                    







                               
// 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

// The pyo3 `pyfunction` proc-macros call unsafe functions internally, which trigger this lint.
#![allow(unsafe_op_in_unsafe_fn)]

use std::ffi::CString;

use log::{Level, MetadataBuilder, Record, logger};
use pyo3::{
    Bound, PyAny, PyResult, Python,
    prelude::{PyAnyMethods, PyListMethods, PyModuleMethods},
    pyfunction, wrap_pyfunction,
};

/// Consume a Python `logging.LogRecord` and emit a Rust `Log` instead.
#[allow(clippy::needless_pass_by_value)]
#[pyfunction]
fn host_log(record: Bound<'_, 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_deref().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.
///
/// # Panics
/// Only if internal assertions fail.
#[allow(clippy::module_name_repetitions)]
pub fn setup_logging(py: Python<'_>, target: &str) -> PyResult<()> {
    let logging = py.import("logging")?;

    logging.setattr("host_log", wrap_pyfunction!(host_log, &logging)?)?;

    py.run(
        CString::new(format!(
            r#"
class HostHandler(Handler):
    def __init__(self, level=0):
        super().__init__(level=level)

    def emit(self, record):
        host_log(record,"{target}")

oldBasicConfig = basicConfig
def basicConfig(*pargs, **kwargs):
    if "handlers" not in kwargs:
        kwargs["handlers"] = [HostHandler()]
    return oldBasicConfig(*pargs, **kwargs)
"#
        ))
        .expect("This is hardcoded")
        .as_c_str(),
        Some(&logging.dict()),
        None,
    )?;

    let all = logging.index()?;
    all.append("HostHandler")?;

    Ok(())
}