[flake8-logging] Root logger calls (LOG015) (#14302)

This commit is contained in:
InSync 2024-11-13 16:11:55 +07:00 committed by GitHub
parent 3e36a7ab81
commit f789b12705
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 330 additions and 0 deletions

View file

@ -0,0 +1,59 @@
# All errors
import logging
logging.debug("Lorem")
logging.info("ipsum")
logging.warn("dolor")
logging.warning("sit")
logging.error("amet")
logging.critical("consectetur")
logging.log("adipiscing")
logging.exception("elit.")
# All errors
from logging import (
debug,
info,
warn,
warning,
error,
critical,
log,
exception
)
debug("Lorem")
info("ipsum")
warn("dolor")
warning("sit")
error("amet")
critical("consectetur")
log("adipiscing")
exception("elit.")
# No errors
logger = logging.getLogger("")
logger.debug("Lorem")
logger.info("ipsum")
logger.warn("dolor")
logger.warning("sit")
logger.error("amet")
logger.critical("consectetur")
logger.log("adipiscing")
logger.exception("elit.")
# No errors
logging = logging.getLogger("")
logging.debug("Lorem")
logging.info("ipsum")
logging.warn("dolor")
logging.warning("sit")
logging.error("amet")
logging.critical("consectetur")
logging.log("adipiscing")
logging.exception("elit.")

View file

@ -994,6 +994,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::ExceptionWithoutExcInfo) { if checker.enabled(Rule::ExceptionWithoutExcInfo) {
flake8_logging::rules::exception_without_exc_info(checker, call); flake8_logging::rules::exception_without_exc_info(checker, call);
} }
if checker.enabled(Rule::RootLoggerCall) {
flake8_logging::rules::root_logger_call(checker, call);
}
if checker.enabled(Rule::IsinstanceTypeNone) { if checker.enabled(Rule::IsinstanceTypeNone) {
refurb::rules::isinstance_type_none(checker, call); refurb::rules::isinstance_type_none(checker, call);
} }

View file

@ -1083,6 +1083,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Logging, "002") => (RuleGroup::Stable, rules::flake8_logging::rules::InvalidGetLoggerArgument), (Flake8Logging, "002") => (RuleGroup::Stable, rules::flake8_logging::rules::InvalidGetLoggerArgument),
(Flake8Logging, "007") => (RuleGroup::Stable, rules::flake8_logging::rules::ExceptionWithoutExcInfo), (Flake8Logging, "007") => (RuleGroup::Stable, rules::flake8_logging::rules::ExceptionWithoutExcInfo),
(Flake8Logging, "009") => (RuleGroup::Stable, rules::flake8_logging::rules::UndocumentedWarn), (Flake8Logging, "009") => (RuleGroup::Stable, rules::flake8_logging::rules::UndocumentedWarn),
(Flake8Logging, "015") => (RuleGroup::Preview, rules::flake8_logging::rules::RootLoggerCall),
_ => return None, _ => return None,
}) })

View file

@ -10,6 +10,7 @@ mod tests {
use crate::assert_messages; use crate::assert_messages;
use crate::registry::Rule; use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
use crate::test::test_path; use crate::test::test_path;
@ -26,4 +27,18 @@ mod tests {
assert_messages!(snapshot, diagnostics); assert_messages!(snapshot, diagnostics);
Ok(()) Ok(())
} }
#[test_case(Rule::RootLoggerCall, Path::new("LOG015.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_logging").join(path).as_path(),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
} }

View file

@ -1,9 +1,11 @@
pub(crate) use direct_logger_instantiation::*; pub(crate) use direct_logger_instantiation::*;
pub(crate) use exception_without_exc_info::*; pub(crate) use exception_without_exc_info::*;
pub(crate) use invalid_get_logger_argument::*; pub(crate) use invalid_get_logger_argument::*;
pub(crate) use root_logger_call::*;
pub(crate) use undocumented_warn::*; pub(crate) use undocumented_warn::*;
mod direct_logger_instantiation; mod direct_logger_instantiation;
mod exception_without_exc_info; mod exception_without_exc_info;
mod invalid_get_logger_argument; mod invalid_get_logger_argument;
mod root_logger_call;
mod undocumented_warn; mod undocumented_warn;

View file

@ -0,0 +1,77 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::ExprCall;
use ruff_python_semantic::Modules;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for usages of the following `logging` top-level functions:
/// `debug`, `info`, `warn`, `warning`, `error`, `critical`, `log`, `exception`.
///
/// ## Why is this bad?
/// Using the root logger causes the messages to have no source information,
/// making them less useful for debugging.
///
/// ## Example
/// ```python
/// import logging
///
/// logging.info("Foobar")
/// ```
///
/// Use instead:
/// ```python
/// import logging
///
/// logger = logging.getLogger(__file__)
/// logger.info("Foobar")
/// ```
#[violation]
pub struct RootLoggerCall {
attr: String,
}
impl Violation for RootLoggerCall {
#[derive_message_formats]
fn message(&self) -> String {
format!("`{}()` call on root logger", self.attr)
}
fn fix_title(&self) -> Option<String> {
Some("Use own logger instead".to_string())
}
}
/// LOG015
pub(crate) fn root_logger_call(checker: &mut Checker, call: &ExprCall) {
let semantic = checker.semantic();
if !semantic.seen_module(Modules::LOGGING) {
return;
}
let Some(qualified_name) = semantic.resolve_qualified_name(&call.func) else {
return;
};
let attr = match qualified_name.segments() {
["logging", attr] if is_logger_method_name(attr) => attr,
_ => return,
};
let kind = RootLoggerCall {
attr: (*attr).to_string(),
};
let diagnostic = Diagnostic::new(kind, call.range);
checker.diagnostics.push(diagnostic);
}
#[inline]
fn is_logger_method_name(attr: &str) -> bool {
matches!(
attr,
"debug" | "info" | "warn" | "warning" | "error" | "critical" | "log" | "exception"
)
}

View file

@ -0,0 +1,171 @@
---
source: crates/ruff_linter/src/rules/flake8_logging/mod.rs
snapshot_kind: text
---
LOG015.py:4:1: LOG015 `debug()` call on root logger
|
2 | import logging
3 |
4 | logging.debug("Lorem")
| ^^^^^^^^^^^^^^^^^^^^^^ LOG015
5 | logging.info("ipsum")
6 | logging.warn("dolor")
|
= help: Use own logger instead
LOG015.py:5:1: LOG015 `info()` call on root logger
|
4 | logging.debug("Lorem")
5 | logging.info("ipsum")
| ^^^^^^^^^^^^^^^^^^^^^ LOG015
6 | logging.warn("dolor")
7 | logging.warning("sit")
|
= help: Use own logger instead
LOG015.py:6:1: LOG015 `warn()` call on root logger
|
4 | logging.debug("Lorem")
5 | logging.info("ipsum")
6 | logging.warn("dolor")
| ^^^^^^^^^^^^^^^^^^^^^ LOG015
7 | logging.warning("sit")
8 | logging.error("amet")
|
= help: Use own logger instead
LOG015.py:7:1: LOG015 `warning()` call on root logger
|
5 | logging.info("ipsum")
6 | logging.warn("dolor")
7 | logging.warning("sit")
| ^^^^^^^^^^^^^^^^^^^^^^ LOG015
8 | logging.error("amet")
9 | logging.critical("consectetur")
|
= help: Use own logger instead
LOG015.py:8:1: LOG015 `error()` call on root logger
|
6 | logging.warn("dolor")
7 | logging.warning("sit")
8 | logging.error("amet")
| ^^^^^^^^^^^^^^^^^^^^^ LOG015
9 | logging.critical("consectetur")
10 | logging.log("adipiscing")
|
= help: Use own logger instead
LOG015.py:9:1: LOG015 `critical()` call on root logger
|
7 | logging.warning("sit")
8 | logging.error("amet")
9 | logging.critical("consectetur")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LOG015
10 | logging.log("adipiscing")
11 | logging.exception("elit.")
|
= help: Use own logger instead
LOG015.py:10:1: LOG015 `log()` call on root logger
|
8 | logging.error("amet")
9 | logging.critical("consectetur")
10 | logging.log("adipiscing")
| ^^^^^^^^^^^^^^^^^^^^^^^^^ LOG015
11 | logging.exception("elit.")
|
= help: Use own logger instead
LOG015.py:11:1: LOG015 `exception()` call on root logger
|
9 | logging.critical("consectetur")
10 | logging.log("adipiscing")
11 | logging.exception("elit.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ LOG015
|
= help: Use own logger instead
LOG015.py:26:1: LOG015 `debug()` call on root logger
|
24 | )
25 |
26 | debug("Lorem")
| ^^^^^^^^^^^^^^ LOG015
27 | info("ipsum")
28 | warn("dolor")
|
= help: Use own logger instead
LOG015.py:27:1: LOG015 `info()` call on root logger
|
26 | debug("Lorem")
27 | info("ipsum")
| ^^^^^^^^^^^^^ LOG015
28 | warn("dolor")
29 | warning("sit")
|
= help: Use own logger instead
LOG015.py:28:1: LOG015 `warn()` call on root logger
|
26 | debug("Lorem")
27 | info("ipsum")
28 | warn("dolor")
| ^^^^^^^^^^^^^ LOG015
29 | warning("sit")
30 | error("amet")
|
= help: Use own logger instead
LOG015.py:29:1: LOG015 `warning()` call on root logger
|
27 | info("ipsum")
28 | warn("dolor")
29 | warning("sit")
| ^^^^^^^^^^^^^^ LOG015
30 | error("amet")
31 | critical("consectetur")
|
= help: Use own logger instead
LOG015.py:30:1: LOG015 `error()` call on root logger
|
28 | warn("dolor")
29 | warning("sit")
30 | error("amet")
| ^^^^^^^^^^^^^ LOG015
31 | critical("consectetur")
32 | log("adipiscing")
|
= help: Use own logger instead
LOG015.py:31:1: LOG015 `critical()` call on root logger
|
29 | warning("sit")
30 | error("amet")
31 | critical("consectetur")
| ^^^^^^^^^^^^^^^^^^^^^^^ LOG015
32 | log("adipiscing")
33 | exception("elit.")
|
= help: Use own logger instead
LOG015.py:32:1: LOG015 `log()` call on root logger
|
30 | error("amet")
31 | critical("consectetur")
32 | log("adipiscing")
| ^^^^^^^^^^^^^^^^^ LOG015
33 | exception("elit.")
|
= help: Use own logger instead
LOG015.py:33:1: LOG015 `exception()` call on root logger
|
31 | critical("consectetur")
32 | log("adipiscing")
33 | exception("elit.")
| ^^^^^^^^^^^^^^^^^^ LOG015
|
= help: Use own logger instead

2
ruff.schema.json generated
View file

@ -3319,6 +3319,8 @@
"LOG002", "LOG002",
"LOG007", "LOG007",
"LOG009", "LOG009",
"LOG01",
"LOG015",
"N", "N",
"N8", "N8",
"N80", "N80",