gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / checkers / logging.py @ 745
History | View | Annotate | Download (10.6 KB)
1 |
# Copyright (c) 2009-2010 Google, Inc.
|
---|---|
2 |
# This program is free software; you can redistribute it and/or modify it under
|
3 |
# the terms of the GNU General Public License as published by the Free Software
|
4 |
# Foundation; either version 2 of the License, or (at your option) any later
|
5 |
# version.
|
6 |
#
|
7 |
# This program is distributed in the hope that it will be useful, but WITHOUT
|
8 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
9 |
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
10 |
#
|
11 |
# You should have received a copy of the GNU General Public License along with
|
12 |
# this program; if not, write to the Free Software Foundation, Inc.,
|
13 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
14 |
"""checker for use of Python logging
|
15 |
"""
|
16 |
|
17 |
import six |
18 |
|
19 |
import astroid |
20 |
|
21 |
from pylint import checkers |
22 |
from pylint import interfaces |
23 |
from pylint.checkers import utils |
24 |
from pylint.checkers.utils import check_messages |
25 |
|
26 |
|
27 |
|
28 |
MSGS = { |
29 |
'W1201': ('Specify string format arguments as logging function parameters', |
30 |
'logging-not-lazy',
|
31 |
'Used when a logging statement has a call form of '
|
32 |
'"logging.<logging method>(format_string % (format_args...))". '
|
33 |
'Such calls should leave string interpolation to the logging '
|
34 |
'method itself and be written '
|
35 |
'"logging.<logging method>(format_string, format_args...)" '
|
36 |
'so that the program may avoid incurring the cost of the '
|
37 |
'interpolation in those cases in which no message will be '
|
38 |
'logged. For more, see '
|
39 |
'http://www.python.org/dev/peps/pep-0282/.'),
|
40 |
'W1202': ('Use % formatting in logging functions and pass the % ' |
41 |
'parameters as arguments',
|
42 |
'logging-format-interpolation',
|
43 |
'Used when a logging statement has a call form of '
|
44 |
'"logging.<logging method>(format_string.format(format_args...))"'
|
45 |
'. Such calls should use % formatting instead, but leave '
|
46 |
'interpolation to the logging function by passing the parameters '
|
47 |
'as arguments.'),
|
48 |
'E1200': ('Unsupported logging format character %r (%#02x) at index %d', |
49 |
'logging-unsupported-format',
|
50 |
'Used when an unsupported format character is used in a logging\
|
51 |
statement format string.'),
|
52 |
'E1201': ('Logging format string ends in middle of conversion specifier', |
53 |
'logging-format-truncated',
|
54 |
'Used when a logging statement format string terminates before\
|
55 |
the end of a conversion specifier.'),
|
56 |
'E1205': ('Too many arguments for logging format string', |
57 |
'logging-too-many-args',
|
58 |
'Used when a logging format string is given too few arguments.'),
|
59 |
'E1206': ('Not enough arguments for logging format string', |
60 |
'logging-too-few-args',
|
61 |
'Used when a logging format string is given too many arguments'),
|
62 |
} |
63 |
|
64 |
|
65 |
CHECKED_CONVENIENCE_FUNCTIONS = set([
|
66 |
'critical', 'debug', 'error', 'exception', 'fatal', 'info', 'warn', |
67 |
'warning'])
|
68 |
|
69 |
def is_method_call(callfunc_node, types=(), methods=()): |
70 |
"""Determines if a CallFunc node represents a method call.
|
71 |
|
72 |
Args:
|
73 |
callfunc_node: The CallFunc AST node to check.
|
74 |
types: Optional sequence of caller type names to restrict check.
|
75 |
methods: Optional sequence of method names to restrict check.
|
76 |
|
77 |
Returns:
|
78 |
True, if the node represents a method call for the given type and
|
79 |
method names, False otherwise.
|
80 |
"""
|
81 |
if not isinstance(callfunc_node, astroid.Call): |
82 |
return False |
83 |
func = utils.safe_infer(callfunc_node.func) |
84 |
return (isinstance(func, astroid.BoundMethod) |
85 |
and isinstance(func.bound, astroid.Instance) |
86 |
and (func.bound.name in types if types else True) |
87 |
and (func.name in methods if methods else True)) |
88 |
|
89 |
|
90 |
|
91 |
class LoggingChecker(checkers.BaseChecker): |
92 |
"""Checks use of the logging module."""
|
93 |
|
94 |
__implements__ = interfaces.IAstroidChecker |
95 |
name = 'logging'
|
96 |
msgs = MSGS |
97 |
|
98 |
options = (('logging-modules',
|
99 |
{'default': ('logging',), |
100 |
'type': 'csv', |
101 |
'metavar': '<comma separated list>', |
102 |
'help': 'Logging modules to check that the string format ' |
103 |
'arguments are in logging function parameter format'}
|
104 |
), |
105 |
) |
106 |
|
107 |
def visit_module(self, node): # pylint: disable=unused-argument |
108 |
"""Clears any state left in this checker from last module checked."""
|
109 |
# The code being checked can just as easily "import logging as foo",
|
110 |
# so it is necessary to process the imports and store in this field
|
111 |
# what name the logging module is actually given.
|
112 |
self._logging_names = set() |
113 |
logging_mods = self.config.logging_modules
|
114 |
|
115 |
self._logging_modules = set(logging_mods) |
116 |
self._from_imports = {}
|
117 |
for logging_mod in logging_mods: |
118 |
parts = logging_mod.rsplit('.', 1) |
119 |
if len(parts) > 1: |
120 |
self._from_imports[parts[0]] = parts[1] |
121 |
|
122 |
def visit_importfrom(self, node): |
123 |
"""Checks to see if a module uses a non-Python logging module."""
|
124 |
try:
|
125 |
logging_name = self._from_imports[node.modname]
|
126 |
for module, as_name in node.names: |
127 |
if module == logging_name:
|
128 |
self._logging_names.add(as_name or module) |
129 |
except KeyError: |
130 |
pass
|
131 |
|
132 |
def visit_import(self, node): |
133 |
"""Checks to see if this module uses Python's built-in logging."""
|
134 |
for module, as_name in node.names: |
135 |
if module in self._logging_modules: |
136 |
self._logging_names.add(as_name or module) |
137 |
|
138 |
@check_messages(*(MSGS.keys()))
|
139 |
def visit_call(self, node): |
140 |
"""Checks calls to logging methods."""
|
141 |
def is_logging_name(): |
142 |
return (isinstance(node.func, astroid.Attribute) and |
143 |
isinstance(node.func.expr, astroid.Name) and |
144 |
node.func.expr.name in self._logging_names) |
145 |
|
146 |
def is_logger_class(): |
147 |
try:
|
148 |
for inferred in node.func.infer(): |
149 |
if isinstance(inferred, astroid.BoundMethod): |
150 |
parent = inferred._proxied.parent |
151 |
if (isinstance(parent, astroid.ClassDef) and |
152 |
(parent.qname() == 'logging.Logger' or |
153 |
any(ancestor.qname() == 'logging.Logger' |
154 |
for ancestor in parent.ancestors()))): |
155 |
return True, inferred._proxied.name |
156 |
except astroid.exceptions.InferenceError:
|
157 |
pass
|
158 |
return False, None |
159 |
|
160 |
if is_logging_name():
|
161 |
name = node.func.attrname |
162 |
else:
|
163 |
result, name = is_logger_class() |
164 |
if not result: |
165 |
return
|
166 |
self._check_log_method(node, name)
|
167 |
|
168 |
def _check_log_method(self, node, name): |
169 |
"""Checks calls to logging.log(level, format, *format_args)."""
|
170 |
if name == 'log': |
171 |
if node.starargs or node.kwargs or len(node.args) < 2: |
172 |
# Either a malformed call, star args, or double-star args. Beyond
|
173 |
# the scope of this checker.
|
174 |
return
|
175 |
format_pos = 1
|
176 |
elif name in CHECKED_CONVENIENCE_FUNCTIONS: |
177 |
if node.starargs or node.kwargs or not node.args: |
178 |
# Either no args, star args, or double-star args. Beyond the
|
179 |
# scope of this checker.
|
180 |
return
|
181 |
format_pos = 0
|
182 |
else:
|
183 |
return
|
184 |
|
185 |
if isinstance(node.args[format_pos], astroid.BinOp) and node.args[format_pos].op == '%': |
186 |
self.add_message('logging-not-lazy', node=node) |
187 |
elif isinstance(node.args[format_pos], astroid.Call): |
188 |
self._check_call_func(node.args[format_pos])
|
189 |
elif isinstance(node.args[format_pos], astroid.Const): |
190 |
self._check_format_string(node, format_pos)
|
191 |
|
192 |
def _check_call_func(self, callfunc_node): |
193 |
"""Checks that function call is not format_string.format().
|
194 |
|
195 |
Args:
|
196 |
callfunc_node: CallFunc AST node to be checked.
|
197 |
"""
|
198 |
if is_method_call(callfunc_node, ('str', 'unicode'), ('format',)): |
199 |
self.add_message('logging-format-interpolation', node=callfunc_node) |
200 |
|
201 |
def _check_format_string(self, node, format_arg): |
202 |
"""Checks that format string tokens match the supplied arguments.
|
203 |
|
204 |
Args:
|
205 |
node: AST node to be checked.
|
206 |
format_arg: Index of the format string in the node arguments.
|
207 |
"""
|
208 |
num_args = _count_supplied_tokens(node.args[format_arg + 1:])
|
209 |
if not num_args: |
210 |
# If no args were supplied, then all format strings are valid -
|
211 |
# don't check any further.
|
212 |
return
|
213 |
format_string = node.args[format_arg].value |
214 |
if not isinstance(format_string, six.string_types): |
215 |
# If the log format is constant non-string (e.g. logging.debug(5)),
|
216 |
# ensure there are no arguments.
|
217 |
required_num_args = 0
|
218 |
else:
|
219 |
try:
|
220 |
keyword_args, required_num_args = \ |
221 |
utils.parse_format_string(format_string) |
222 |
if keyword_args:
|
223 |
# Keyword checking on logging strings is complicated by
|
224 |
# special keywords - out of scope.
|
225 |
return
|
226 |
except utils.UnsupportedFormatCharacter as ex: |
227 |
char = format_string[ex.index] |
228 |
self.add_message('logging-unsupported-format', node=node, |
229 |
args=(char, ord(char), ex.index))
|
230 |
return
|
231 |
except utils.IncompleteFormatString:
|
232 |
self.add_message('logging-format-truncated', node=node) |
233 |
return
|
234 |
if num_args > required_num_args:
|
235 |
self.add_message('logging-too-many-args', node=node) |
236 |
elif num_args < required_num_args:
|
237 |
self.add_message('logging-too-few-args', node=node) |
238 |
|
239 |
|
240 |
def _count_supplied_tokens(args): |
241 |
"""Counts the number of tokens in an args list.
|
242 |
|
243 |
The Python log functions allow for special keyword arguments: func,
|
244 |
exc_info and extra. To handle these cases correctly, we only count
|
245 |
arguments that aren't keywords.
|
246 |
|
247 |
Args:
|
248 |
args: List of AST nodes that are arguments for a log format string.
|
249 |
|
250 |
Returns:
|
251 |
Number of AST nodes that aren't keywords.
|
252 |
"""
|
253 |
return sum(1 for arg in args if not isinstance(arg, astroid.Keyword)) |
254 |
|
255 |
|
256 |
def register(linter): |
257 |
"""Required method to auto-register this checker."""
|
258 |
linter.register_checker(LoggingChecker(linter)) |