1、概述
scrapy中的日志模块使用了python中logging库实现。是logging库的典型应用。我们可以分析其使用方法,借鉴设计方法,提取使用技巧,来优化我们自己开发项目的日志模块设计。
我把scrapy中的日志模块设计分为三篇文章介绍,此篇为下篇
Scrapy: log日志模块的设计详解上
这篇文章主要介绍了logging库本身的一些概念,由于篇幅有限,scrapy源码中的logging用法将在这篇文章中介绍
Scrapy: log日志模块的设计详解中
中篇详细介绍了scrapy中日志模块的核心配置,这篇文章可以算为中篇的续集,继续介绍一些中篇中没有介绍到的方法
2、代码解析
2.1、log_scrapy_info
我们用scrapy的时候都会发现,在启动程序的时候,会直接打印出来一些当前scrapy版本,还有一些关键依赖包的信息。这些信息就是通过这个log_scrapy_info实现的。
这个函数在scrapy/crawler.py中CrawlerProcess对象初始化的时候调用。
"""
在crawler中__init__的时候设置好configure_logging后,打印的一些scrapy的一些基础信息
这里面的logger就是本文件中的记录器,就是一次日志的正常打印
"""
def log_scrapy_info(settings: Settings) -> None:
logger.info(
"Scrapy %(version)s started (bot: %(bot)s)",
{"version": scrapy.__version__, "bot": settings["BOT_NAME"]},
)
versions = [
f"{name} {version}"
for name, version in scrapy_components_versions()
if name != "Scrapy"
]
logger.info("Versions: %(versions)s", {"versions": ", ".join(versions)})
2.2、log_reactor_info
顾名思义,和log_scrapy_info干的事情差不多,这个方法是打印出twisted中的反应堆reactor的一些信息
"""
打印twisted的反应堆的基本信息
"""
def log_reactor_info() -> None:
from twisted.internet import reactor
logger.debug("Using reactor: %s.%s", reactor.__module__, reactor.__class__.__name__)
from twisted.internet import asyncioreactor
if isinstance(reactor, asyncioreactor.AsyncioSelectorReactor):
logger.debug(
"Using asyncio event loop: %s.%s",
reactor._asyncioEventloop.__module__,
reactor._asyncioEventloop.__class__.__name__,
)
2.3、LogCounterHandler
自定义了一个记录器。继承自logging.Handler。主要是实现了抽象方法emit的逻辑。具体细节在介绍crawler对象的时候再详细阐述
"""
用来记录日志数量的处理器
需要了解下crawler.stats的结构,是如何存储这些计数数据的
"""
class LogCounterHandler(logging.Handler):
"""Record log levels count into a crawler stats"""
def __init__(self, crawler: Crawler, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.crawler: Crawler = crawler
"""
emit方法slogging.Handler中抽象方法,必须在其子类中实现,用于实现日志记录的逻辑。
这里emit方法借助crawler的stats记录不同级别的日志数量。详情在介绍crawler的文章中介绍
"""
def emit(self, record: logging.LogRecord) -> None:
sname = f"log_count/{record.levelname}"
assert self.crawler.stats
self.crawler.stats.inc_value(sname)
2.4、logformatter_adapter
顾名思义,是个日子格式适配器,为了实现日志格式统一的工具。在讲解scraper对象的时候再做详细分析
"""
为了把其他格式的数据,转化为log的格式。所以是个适配器
在scraper中有调用,还传入一些遇到错误的时候的参数,研究到scraper的时候再仔细研究这个方法
另外关于log的各种参数的描述,用得到
https://docs.python.org/zh-cn/3.9/library/logging.html?highlight=logging%20log#logging.log
"""
def logformatter_adapter(
logkws: LogFormatterResult,
) -> tuple[int, str, dict[str, Any] | tuple[Any, ...]]:
"""
Helper that takes the dictionary output from the methods in LogFormatter
and adapts it into a tuple of positional arguments for logger.log calls,
handling backward compatibility as well.
"""
level = logkws.get("level", logging.INFO)
message = logkws.get("msg") or ""
# NOTE: This also handles 'args' being an empty dict, that case doesn't
# play well in logger.log calls
args = cast(dict[str, Any], logkws) if not logkws.get("args") else logkws["args"]
return (level, message, args)
2.5、SpiderLoggerAdapter
顾名思义,是spider对象的日志输出适配器。介绍spider对象的文章中再做详细介绍
"""
在scrapy/spiders/__init__.py中调用的。用于给spider生成一个记录器对象,具体到研究到spider的时候再仔细研究这个
LoggerAdapter的官方链接
https://docs.python.org/zh-cn/3.9/library/logging.html?highlight=loggeradapter#logging.LoggerAdapter
"""
class SpiderLoggerAdapter(logging.LoggerAdapter):
def process(
self, msg: str, kwargs: MutableMapping[str, Any]
) -> tuple[str, MutableMapping[str, Any]]:
"""Method that augments logging with additional 'extra' data"""
if isinstance(kwargs.get("extra"), MutableMapping):
kwargs["extra"].update(self.extra)
else:
kwargs["extra"] = self.extra
return msg, kwargs
总结
这篇文件主要介绍几个比较独立的函数方法。包括打印scrapy基本信息的方法。打印twisted基本信息的方法。还有在scraper、spider对象中使用日志适配器。另外还有一个自己实现的日志数量记录器LogCounterHandler。当然,这些方法还要结合其他对象来使用。后面我会详细为大家讲解!