sys.stdout = StreamLogger(logging.getLogger("stdout")) # type: ignore[assignment]
# 将标准输出改为 logger
该函数在 `install_root_handler` 为 True (默认)的情况下会调用 `install_scrapy_root_handler`
---
### install\_scrapy\_root\_handler
`install_scrapy_root_handler` 该函数会给 `logging.root` 添加一个 handler(由 `_get_handler` 从 settings 中读取配置)
scrapy/utils/log.py#143
settings 为从 settings.py 读取的配置信息
def _get_handler(settings: Settings) -> logging.Handler:
“”“Return a log handler object according to settings”“”
filename = settings.get(“LOG_FILE”) # 输出的日志文件名称
handler: logging.Handler
if filename:
mode = “a” if settings.getbool(“LOG_FILE_APPEND”) else “w” # 日志文件模式
encoding = settings.get(“LOG_ENCODING”) # 日志文件编码
handler = logging.FileHandler(filename, mode=mode, encoding=encoding)
elif settings.getbool(“LOG_ENABLED”): # 是否启用日志
handler = logging.StreamHandler() # 标记1
else:
handler = logging.NullHandler()
formatter = logging.Formatter( # 设置日志格式
fmt=settings.get("LOG\_FORMAT"), datefmt=settings.get("LOG\_DATEFORMAT")
)
handler.setFormatter(formatter)
handler.setLevel(settings.get("LOG\_LEVEL")) # 设置日志最低级别
if settings.getbool("LOG\_SHORT\_NAMES"): # 是否缩写
handler.addFilter(TopLevelFormatter(["scrapy"]))
return handler
scrapy/utils/log.py#125
设置 scrapy 的 handler
def install_scrapy_root_handler(settings: Settings) -> None:
global _scrapy_root_handler
if (
_scrapy_root_handler is not None
and _scrapy_root_handler in logging.root.handlers
):
logging.root.removeHandler(_scrapy_root_handler)
logging.root.setLevel(logging.NOTSET)
_scrapy_root_handler = _get_handler(settings)
logging.root.addHandler(_scrapy_root_handler)
* 标记1:scrapy 使用的是 `logging.StreamHandler`,该 `StreamHandler` 默认使用的就是 `sys.stderr`
---
### 分析
`configure_logging` 会在 `scrapy.CrawlerProcess` 中调用:
scrapy/crawler.py 328
class CrawlerProcess(CrawlerRunner):
def \_\_init\_\_(
self,
settings: Union[Dict[str, Any], Settings, None] = None,
install_root_handler: bool = True,
):
...
configure_logging(self.settings, install_root_handler)
...
其中: `install_root_handler` 默认值为 True
而 `CrawlerProcess` 只有在 `cmdline` 中被调用:
scrapy/cmdline.py #159
def execute(argv=None, settings=None):
…
cmd.crawler_process = CrawlerProcess(settings)
…
>
> 这部分在之前的命令分析文章中提到,是命令执行的关键函数,框架的启动必定会调用该函数;
>
>
>
那么 `scrapy_root_handler` 必定会被添加到 `logging.root.handlers` 中,所有日志都会经过该 handler 进行输出
---
## 实现
既然已经知道 scrapy 的日志输出是通过将 `_scrapy_root_handler` 添加到 `logging.root.handlers` 中实现的,那可以考虑如何解决在 pycharm 中显示为 红色的问题了;
有3种思路:
1. pycharm 中其实是可以设置 `stderr` 输出样式的(默认为红色)(弊端为 `sys.stderr` 中非日志部分的样式不是红色,分辨不出来)
2. 修改 handlers 的内容,将 stream 设置为 `sys.stdout`(弊端为当 `LOG_STDOUT` 为 True 时会产生递归异常)
3. 将 `install_root_handler` 设置为 `False`(这样就会导致 settings.py 的配置信息无效,需要自己读取设置才行)
### 第1种:修改Pycharm配置
Pycharm中依次点击:File -> Settings -> Editor -> Color Schema -> Console Colors -> Console -> Error outpt
然后就能设置对应的颜色了
---
### 第2种:返回自定义handler
可以通过hack的方式修改:
在 `settings.py` 加入:
import scrapy.utils.log as log
拿到原函数
_get_handler = copy.copy(log._get_handler)
自己修改过后的
def get_handler_custom(settings: Settings):
handler = _get_handler(settings)
# 如果是 StreamHandler,就修改为 sys.stdout
if isinstance(handler, logging.StreamHandler):
handler.setStream(sys.stdout)
return handler
覆盖原来的函数
log._get_handler = get_handler_custom
注意不能设置 `LOG_STDOUT` 为 True,会递归爆栈的
分析:
* 在 `configure_logging` 中有这么一行代码
if settings.getbool(“LOG_STDOUT”):
sys.stdout = StreamLogger(logging.getLogger(“stdout”)) # type: ignore[assignment]
是直接将标准输出定为一个名称为 stdout 的 StreamLogger 了
StreamLogger 有这样一个方法,将 buf 用 日志输出了
def write(self, buf: str) -> None:
for line in buf.rstrip().splitlines():
self.logger.log(self.log_level, line.rstrip())
* 看 `logging.StreamHandler`的代码:
此时先将 `sys.stdout` 设置为 `StreamLogger` 了
然后我们上面的代码将 `stream` 设置为 `sys.stdout`
def flush(self):
self.acquire()
try:
if self.stream and hasattr(self.stream, “flush”):
self.stream.flush()
finally:
self.release()
这样产生递归了,没有出口,就爆栈了
>
> 可以自己返回自定义的 handler,但是日志配置信息还是得自己读取,settings参数已经有了,直接模拟源码读取即可
>
>
>
---
### 第3种:日志着色
我当前写的项目中,是项目外部启动(不使用 Scrapy 命令,而是 CrawlerProcess 启动)
那么我直接传入 `install_root_handler` 为 False 即可
def run_spider(spider_name: str, _settings):
crawler_process = CrawlerProcess(settings, install_root_handler=False)
crawl_defer = crawler_process.crawl(spider_name)
if getattr(crawl_defer, “result”, None) is not None and issubclass(
crawl_defer.result.type, Exception
):
exitcode = 1
else:
crawler_process.start()
if (
crawler_process.bootstrap_failed
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)
png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)