use pyo3::{prelude::*, types::PyTuple}; use crate::{ arbitrary::{arbitrary::Arbitrary, map::Map}, python_utils::{detach, validate_type_by_name}, }; #[derive(Debug)] pub struct HassLogger(Py); impl<'source> FromPyObject<'source> for HassLogger { fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult { // region: Validation validate_type_by_name(ob, "HassLogger")?; // endregion: Validation Ok(Self(detach(ob))) } } #[derive(Debug, Clone, IntoPyObject)] pub struct LogData { /// If exc_info does not evaluate as false, it causes exception information to be added to the logging message. /// If an exception tuple (in the format returned by sys.exc_info()) or an exception instance is provided, it is used; /// otherwise, sys.exc_info() is called to get the exception information. exc_info: Option, /// If true, stack information is added to the logging message, including the actual logging call. /// Note that this is not the same stack information as that displayed through specifying exc_info: /// The former is stack frames from the bottom of the stack up to the logging call in the current thread, /// whereas the latter is information about stack frames which have been unwound, /// following an exception, while searching for exception handlers. /// /// You can specify stack_info independently of exc_info, /// e.g. to just show how you got to a certain point in your code, even when no exceptions were raised. /// The stack frames are printed following a header line which says: /// /// Stack (most recent call last): /// /// This mimics the `Traceback (most recent call last):` which is used when displaying exception frames. stack_info: bool, /// If greater than 1, the corresponding number of stack frames are skipped /// when computing the line number and function name set in the LogRecord created for the logging event. /// This can be used in logging helpers so that the function name, filename and line number recorded /// are not the information for the helper function/method, but rather its caller. stacklevel: u16, /// This can be used to pass a dictionary which is used to populate the __dict__ of the LogRecord /// created for the logging event with user-defined attributes. /// These custom attributes can then be used as you like. /// For example, they could be incorporated into logged messages. extra: Map, } impl HassLogger { pub fn new(py: Python<'_>, name: &str) -> PyResult { let logging = py.import("logging")?; let logger = logging.call_method1("getLogger", (name,))?; Ok(logger.extract()?) } pub fn debug<'py, ExcInfo: IntoPyObject<'py>>( &self, py: Python<'py>, msg: &str, args: Vec, log_data: Option>, ) -> PyResult<()> { let mut all_args = vec![msg.into_pyobject(py)?.into_any()]; for arg in args { let arg = arg.into_pyobject(py)?; all_args.push(arg); } let all_args = PyTuple::new(py, all_args)?; let kwargs = log_data .map(|log_data| log_data.into_pyobject(py)) .transpose()?; self.0.call_method(py, "debug", all_args, kwargs.as_ref())?; Ok(()) } pub fn info<'py, ExcInfo: IntoPyObject<'py>>( &self, py: Python<'py>, msg: &str, args: Vec, log_data: Option>, ) -> PyResult<()> { let mut all_args = vec![msg.into_pyobject(py)?.into_any()]; for arg in args { let arg = arg.into_pyobject(py)?; all_args.push(arg); } let all_args = PyTuple::new(py, all_args)?; let kwargs = log_data .map(|log_data| log_data.into_pyobject(py)) .transpose()?; self.0.call_method(py, "info", all_args, kwargs.as_ref())?; Ok(()) } pub fn warning<'py, ExcInfo: IntoPyObject<'py>>( &self, py: Python<'py>, msg: &str, args: Vec, log_data: Option>, ) -> PyResult<()> { let mut all_args = vec![msg.into_pyobject(py)?.into_any()]; for arg in args { let arg = arg.into_pyobject(py)?; all_args.push(arg); } let all_args = PyTuple::new(py, all_args)?; let kwargs = log_data .map(|log_data| log_data.into_pyobject(py)) .transpose()?; self.0 .call_method(py, "warning", all_args, kwargs.as_ref())?; Ok(()) } pub fn error<'py, ExcInfo: IntoPyObject<'py>>( &self, py: Python<'py>, msg: &str, args: Vec, log_data: Option>, ) -> PyResult<()> { let mut all_args = vec![msg.into_pyobject(py)?.into_any()]; for arg in args { let arg = arg.into_pyobject(py)?; all_args.push(arg); } let all_args = PyTuple::new(py, all_args)?; let kwargs = log_data .map(|log_data| log_data.into_pyobject(py)) .transpose()?; self.0.call_method(py, "error", all_args, kwargs.as_ref())?; Ok(()) } pub fn critical<'py, ExcInfo: IntoPyObject<'py>>( &self, py: Python<'py>, msg: &str, args: Vec, log_data: Option>, ) -> PyResult<()> { let mut all_args = vec![msg.into_pyobject(py)?.into_any()]; for arg in args { let arg = arg.into_pyobject(py)?; all_args.push(arg); } let all_args = PyTuple::new(py, all_args)?; let kwargs = log_data .map(|log_data| log_data.into_pyobject(py)) .transpose()?; self.0 .call_method(py, "critical", all_args, kwargs.as_ref())?; Ok(()) } }