feat: HassLogger

This commit is contained in:
2025-03-15 15:30:03 -04:00
parent 70c2380d0c
commit 8099e5b8cd
2 changed files with 181 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
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<PyAny>);
impl<'source> FromPyObject<'source> for HassLogger {
fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult<Self> {
// region: Validation
validate_type_by_name(ob, "HassLogger")?;
// endregion: Validation
Ok(Self(detach(ob)))
}
}
#[derive(Debug, Clone, IntoPyObject)]
pub struct LogData<ExcInfo> {
/// 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<ExcInfo>,
/// 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<Self> {
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<Arbitrary>,
log_data: Option<LogData<ExcInfo>>,
) -> 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<Arbitrary>,
log_data: Option<LogData<ExcInfo>>,
) -> 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<Arbitrary>,
log_data: Option<LogData<ExcInfo>>,
) -> 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<Arbitrary>,
log_data: Option<LogData<ExcInfo>>,
) -> 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<Arbitrary>,
log_data: Option<LogData<ExcInfo>>,
) -> 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(())
}
}

View File

@@ -2,6 +2,7 @@ pub mod domain;
pub mod entity_id; pub mod entity_id;
pub mod event; pub mod event;
pub mod home_assistant; pub mod home_assistant;
pub mod logger;
pub mod object_id; pub mod object_id;
pub mod state; pub mod state;
pub mod state_machine; pub mod state_machine;