mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
Allow specification of logging.Logger
re-exports via logger-objects
(#5750)
## Summary This PR adds a `logger-objects` setting that allows users to mark specific symbols a `logging.Logger` objects. Currently, if a `logger` is imported, we only flagged it as a `logging.Logger` if it comes exactly from the `logging` module or is `flask.current_app.logger`. This PR allows users to mark specific loggers, like `logging_setup.logger`, to ensure that they're covered by the `flake8-logging-format` rules and others. For example, if you have a module `logging_setup.py` with the following contents: ```python import logging logger = logging.getLogger(__name__) ``` Adding `"logging_setup.logger"` to `logger-objects` will ensure that `logging_setup.logger` is treated as a `logging.Logger` object when imported from other modules (e.g., `from logging_setup import logger`). Closes https://github.com/astral-sh/ruff/issues/5694.
This commit is contained in:
parent
727153cf45
commit
f9726af4ef
16 changed files with 197 additions and 104 deletions
|
@ -1,7 +1,7 @@
|
|||
use rustpython_parser::ast::{self, Constant, Expr, Keyword};
|
||||
use rustpython_parser::ast::{self, Expr, Keyword};
|
||||
|
||||
use ruff_python_ast::call_path::collect_call_path;
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::call_path::{collect_call_path, from_qualified_name};
|
||||
use ruff_python_ast::helpers::{find_keyword, is_const_true};
|
||||
|
||||
use crate::model::SemanticModel;
|
||||
|
||||
|
@ -9,37 +9,53 @@ use crate::model::SemanticModel;
|
|||
/// `logging.error`, `logger.error`, `self.logger.error`, etc., but not
|
||||
/// arbitrary `foo.error` calls.
|
||||
///
|
||||
/// It even matches direct `logging.error` calls even if the `logging` module
|
||||
/// It also matches direct `logging.error` calls when the `logging` module
|
||||
/// is aliased. Example:
|
||||
/// ```python
|
||||
/// import logging as bar
|
||||
///
|
||||
/// # This is detected to be a logger candidate
|
||||
/// # This is detected to be a logger candidate.
|
||||
/// bar.error()
|
||||
/// ```
|
||||
pub fn is_logger_candidate(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
if let Expr::Attribute(ast::ExprAttribute { value, .. }) = func {
|
||||
let Some(call_path) = (if let Some(call_path) = semantic.resolve_call_path(value) {
|
||||
if call_path
|
||||
.first()
|
||||
.map_or(false, |module| *module == "logging")
|
||||
|| call_path.as_slice() == ["flask", "current_app", "logger"]
|
||||
{
|
||||
Some(call_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
collect_call_path(value)
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
pub fn is_logger_candidate(
|
||||
func: &Expr,
|
||||
semantic: &SemanticModel,
|
||||
logger_objects: &[String],
|
||||
) -> bool {
|
||||
let Expr::Attribute(ast::ExprAttribute { value, .. }) = func else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// If the symbol was imported from another module, ensure that it's either a user-specified
|
||||
// logger object, the `logging` module itself, or `flask.current_app.logger`.
|
||||
if let Some(call_path) = semantic.resolve_call_path(value) {
|
||||
if matches!(
|
||||
call_path.as_slice(),
|
||||
["logging"] | ["flask", "current_app", "logger"]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if logger_objects
|
||||
.iter()
|
||||
.any(|logger| from_qualified_name(logger) == call_path)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, if the symbol was defined in the current module, match against some common
|
||||
// logger names.
|
||||
if let Some(call_path) = collect_call_path(value) {
|
||||
if let Some(tail) = call_path.last() {
|
||||
if tail.starts_with("log") || tail.ends_with("logger") || tail.ends_with("logging") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -49,23 +65,20 @@ pub fn exc_info<'a>(keywords: &'a [Keyword], semantic: &SemanticModel) -> Option
|
|||
let exc_info = find_keyword(keywords, "exc_info")?;
|
||||
|
||||
// Ex) `logging.error("...", exc_info=True)`
|
||||
if matches!(
|
||||
exc_info.value,
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(true),
|
||||
..
|
||||
})
|
||||
) {
|
||||
if is_const_true(&exc_info.value) {
|
||||
return Some(exc_info);
|
||||
}
|
||||
|
||||
// Ex) `logging.error("...", exc_info=sys.exc_info())`
|
||||
if let Expr::Call(ast::ExprCall { func, .. }) = &exc_info.value {
|
||||
if semantic.resolve_call_path(func).map_or(false, |call_path| {
|
||||
if exc_info
|
||||
.value
|
||||
.as_call_expr()
|
||||
.and_then(|call| semantic.resolve_call_path(&call.func))
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["sys", "exc_info"])
|
||||
}) {
|
||||
return Some(exc_info);
|
||||
}
|
||||
})
|
||||
{
|
||||
return Some(exc_info);
|
||||
}
|
||||
|
||||
None
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue