我是新手,所以使用我在网上找到的一个例子来添加一些自定义日志记录级别。这将包含在一个库中,该库将被导入到各种脚本中。它按预期工作,但添加的级别未显示在自动完成列表中(使用 PyCharm),并且 PyCharm 抱怨 LOGGER 中存在未解析的属性引用。当我编码并输入“LOGGER”时。我看到正常的错误、警告、信息等可供选择,但我的自定义级别“详细”不在列表中。随着时间的推移,将会添加更多自定义级别,这也将推出给一个开发团队,所以我需要让这个工作。
知道为什么我的自动完成列表中没有详细选项吗?
这是我的文件。
px_logger.py
from logging import getLoggerClass, addLevelName, setLoggerClass, NOTSET
public class PxLogger(getLoggerClass()):
def __init__(self, name, level=NOTSET):
super(PxLogger, self).__init__(name, level)
addLevelName(5, "VERBOSE")
def verbose(self, msg, *args, **kwargs):
"""Custom logger level - verbose"""
if self.isEnabledFor(5):
self._log(5, msg, args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
my_script.py
import json
import logging.config
from px_logger import PxLogger
logging.setLoggerClass(PxLogger)
LOGGER = logging.getLogger(__name__)
with open('../logging.json') as f: # load logging config file
CONFIG_DICT = json.load(f)
logging.config.dictConfig(CONFIG_DICT)
LOGGER.verbose('Test verbose message')
Run Code Online (Sandbox Code Playgroud)
屏幕输出
VERBOSE - Test verbose message
Run Code Online (Sandbox Code Playgroud)
PyCharm offers various ways to accomplish type hinting
Internally, it uses Typeshed to determine the types of standard lib objects and common 3rd party packages.
That's also where PyCharm takes the type of the return value for logging.getLogger from and that's why it does not show your subclass' verbose method in autocomplete, because it assumes LOGGER to be an instance of logging.Logger.
The easiest way to tell PyCharm's type checker that LOGGER is an instance of PxLogger would be a type annotation in the code during assignment. This works in Python 3.5+ only:
LOGGER: PxLogger = logging.getLogger(__name__)
Run Code Online (Sandbox Code Playgroud)
If you went one step further, you would encapsulate the definition of your custom logger class, it being assigned as global logger class and the definition of a wrapper for logging.getLogger inside your module.
This would enable your coworkers to just import your module instead of logging and use it just as they would with the original logging without having to worry about which class to set as logger class or how to annotate the variable that holds their logger.
There's three options to include type hinting for the type checker when going down this road.
px_logger.py
# basically, import from logging whatever you may need and overwrite where necessary
from logging import getLogger as _getLogger, Logger, addLevelName, setLoggerClass, NOTSET
from typing import Optional # this only for the Python 3.5+ solution
class PxLogger(Logger): # Note: subclass logging.Logger explicitly
def __init__(self, name, level=NOTSET):
super(PxLogger, self).__init__(name, level)
addLevelName(5, "VERBOSE")
def verbose(self, msg, *args, **kwargs):
"""Custom logger level - verbose"""
if self.isEnabledFor(5):
self._log(5, msg, args, **kwargs)
setLoggerClass(PxLogger)
"""
Possible approaches, implement one of the below.
The first is Python 3.5+ only.
The second and third work for both, Python 2 and Python 3.
"""
# using type annotation syntax (py35+)
def getLogger(name: Optional[str]=None) -> PxLogger:
_logr: PxLogger = _getLogger(name)
return _logr
# using (legacy) docstring syntax (py2and3)
def getLogger(name=None)
"""
:param name: str
:rtype: PxLogger
"""
return _getLogger(name)
# using a stub file (py2and3)
def getLogger(name=None):
return _getLogger(name)
Run Code Online (Sandbox Code Playgroud)
The Python 2and3 stub file approach requires a file named py_logger.pyi next to the actual module file px_logger.py in your package.
px_logger.pyi
# The PEP-484 syntax does not matter here.
# The Python interpreter will ignore this file,
# it is only relevant for the static type checker
import logging
class PxLogger(logging.Logger):
def verbose(self, msg, *args, **kwargs) -> None: ...
def getLogger(name) -> PxLogger: ...
Run Code Online (Sandbox Code Playgroud)
For all three approaches, your module my_script would look the same:
my_script.py
import logging.config
import px_logger
LOGGER = px_logger.getLogger(__name__)
# I used basicConfig here for simplicity, dictConfig should work just as well
logging.basicConfig(level=5,
format='%(asctime)s - %(levelname)s [%(filename)s]: %(name)s %(funcName)20s - Message: %(message)s',
datefmt='%d.%m.%Y %H:%M:%S',
filename='myapp.log',
filemode='a')
LOGGER.verbose('Test verbose message')
Run Code Online (Sandbox Code Playgroud)
Autocomplete works well with all three approaches:
Approach two and three have been tested with Python 2.7.15 and 3.6.5 in PyCharm 2018.1 CE
NOTE:
In a previous revision of this answer I stated that the docstring approach, although showing the custom verbose() method of your PxLogger class, is missing the base class' methods. That is because you derive PxLogger from whatever logging.getLoggerClass returns, i.e. an arbitrary class.
If you make PxLogger a subclass of logging.Logger explicitly, the type checker knows the base class and can correctly resolve its methods for autocompletion.
I do not recommend subclassing the return value of getLoggerClass anyway. You'll want to be sure what you derive from and not rely on a function returning the correct thing.
| 归档时间: |
|
| 查看次数: |
324 次 |
| 最近记录: |