import unittest from testfixtures import LogCapture from twisted.internet import defer from twisted.python.failure import Failure from twisted.trial.unittest import TestCase as TwistedTestCase from scrapy.exceptions import DropItem from scrapy.utils.test import get_crawler from scrapy.http import Request, Response from scrapy.item import Item, Field from scrapy.logformatter import LogFormatter from scrapy.spiders import Spider from tests.mockserver import MockServer from tests.spiders import ItemSpider class CustomItem(Item): name = Field() def __str__(self): return f"name: {self['name']}" class LogFormatterTestCase(unittest.TestCase): def setUp(self): self.formatter = LogFormatter() self.spider = Spider("default") def test_crawled_with_referer(self): req = Request("http://www.example.com") res = Response("http://www.example.com") logkws = self.formatter.crawled(req, res, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Crawled (200) (referer: None)" ) def test_crawled_without_referer(self): req = Request( "http://www.example.com", headers={"referer": "http://example.com"} ) res = Response("http://www.example.com", flags=["cached"]) logkws = self.formatter.crawled(req, res, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Crawled (200) (referer: http://example.com) ['cached']", ) def test_flags_in_request(self): req = Request("http://www.example.com", flags=["test", "flag"]) res = Response("http://www.example.com") logkws = self.formatter.crawled(req, res, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Crawled (200) ['test', 'flag'] (referer: None)", ) def test_dropped(self): item = {} exception = Exception("\u2018") response = Response("http://www.example.com") logkws = self.formatter.dropped(item, exception, response, self.spider) logline = logkws["msg"] % logkws["args"] lines = logline.splitlines() assert all(isinstance(x, str) for x in lines) self.assertEqual(lines, ["Dropped: \u2018", "{}"]) def test_item_error(self): # In practice, the complete traceback is shown by passing the # 'exc_info' argument to the logging function item = {"key": "value"} exception = Exception() response = Response("http://www.example.com") logkws = self.formatter.item_error(item, exception, response, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual(logline, "Error processing {'key': 'value'}") def test_spider_error(self): # In practice, the complete traceback is shown by passing the # 'exc_info' argument to the logging function failure = Failure(Exception()) request = Request( "http://www.example.com", headers={"Referer": "http://example.org"} ) response = Response("http://www.example.com", request=request) logkws = self.formatter.spider_error(failure, request, response, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Spider error processing (referer: http://example.org)", ) def test_download_error_short(self): # In practice, the complete traceback is shown by passing the # 'exc_info' argument to the logging function failure = Failure(Exception()) request = Request("http://www.example.com") logkws = self.formatter.download_error(failure, request, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual(logline, "Error downloading ") def test_download_error_long(self): # In practice, the complete traceback is shown by passing the # 'exc_info' argument to the logging function failure = Failure(Exception()) request = Request("http://www.example.com") logkws = self.formatter.download_error( failure, request, self.spider, "Some message" ) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Error downloading : Some message" ) def test_scraped(self): item = CustomItem() item["name"] = "\xa3" response = Response("http://www.example.com") logkws = self.formatter.scraped(item, response, self.spider) logline = logkws["msg"] % logkws["args"] lines = logline.splitlines() assert all(isinstance(x, str) for x in lines) self.assertEqual( lines, ["Scraped from <200 http://www.example.com>", "name: \xa3"] ) class LogFormatterSubclass(LogFormatter): def crawled(self, request, response, spider): kwargs = super().crawled(request, response, spider) CRAWLEDMSG = "Crawled (%(status)s) %(request)s (referer: %(referer)s) %(flags)s" log_args = kwargs["args"] log_args["flags"] = str(request.flags) return { "level": kwargs["level"], "msg": CRAWLEDMSG, "args": log_args, } class LogformatterSubclassTest(LogFormatterTestCase): def setUp(self): self.formatter = LogFormatterSubclass() self.spider = Spider("default") def test_crawled_with_referer(self): req = Request("http://www.example.com") res = Response("http://www.example.com") logkws = self.formatter.crawled(req, res, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Crawled (200) (referer: None) []" ) def test_crawled_without_referer(self): req = Request( "http://www.example.com", headers={"referer": "http://example.com"}, flags=["cached"], ) res = Response("http://www.example.com") logkws = self.formatter.crawled(req, res, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Crawled (200) (referer: http://example.com) ['cached']", ) def test_flags_in_request(self): req = Request("http://www.example.com", flags=["test", "flag"]) res = Response("http://www.example.com") logkws = self.formatter.crawled(req, res, self.spider) logline = logkws["msg"] % logkws["args"] self.assertEqual( logline, "Crawled (200) (referer: None) ['test', 'flag']", ) class SkipMessagesLogFormatter(LogFormatter): def crawled(self, *args, **kwargs): return None def scraped(self, *args, **kwargs): return None def dropped(self, *args, **kwargs): return None class DropSomeItemsPipeline: drop = True def process_item(self, item, spider): if self.drop: self.drop = False raise DropItem("Ignoring item") else: self.drop = True class ShowOrSkipMessagesTestCase(TwistedTestCase): def setUp(self): self.mockserver = MockServer() self.mockserver.__enter__() self.base_settings = { "LOG_LEVEL": "DEBUG", "ITEM_PIPELINES": { DropSomeItemsPipeline: 300, }, } def tearDown(self): self.mockserver.__exit__(None, None, None) @defer.inlineCallbacks def test_show_messages(self): crawler = get_crawler(ItemSpider, self.base_settings) with LogCapture() as lc: yield crawler.crawl(mockserver=self.mockserver) self.assertIn("Scraped from <200 http://127.0.0.1:", str(lc)) self.assertIn("Crawled (200)