Add failed and success count stats to feedstorage backends (#4850)

This commit is contained in:
joaquin garmendia 2020-11-11 15:16:01 -05:00 committed by GitHub
parent c20b34269f
commit 85604e1078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 110 additions and 18 deletions

View File

@ -319,18 +319,26 @@ class FeedExporter:
# Use `largs=log_args` to copy log_args into function's scope
# instead of using `log_args` from the outer scope
d.addCallback(
lambda _, largs=log_args: logger.info(
logfmt % "Stored", largs, extra={'spider': spider}
)
self._handle_store_success, log_args, logfmt, spider, type(slot.storage).__name__
)
d.addErrback(
lambda f, largs=log_args: logger.error(
logfmt % "Error storing", largs,
exc_info=failure_to_exc_info(f), extra={'spider': spider}
)
self._handle_store_error, log_args, logfmt, spider, type(slot.storage).__name__
)
return d
def _handle_store_error(self, f, largs, logfmt, spider, slot_type):
logger.error(
logfmt % "Error storing", largs,
exc_info=failure_to_exc_info(f), extra={'spider': spider}
)
self.crawler.stats.inc_value(f"feedexport/failed_count/{slot_type}")
def _handle_store_success(self, f, largs, logfmt, spider, slot_type):
logger.info(
logfmt % "Stored", largs, extra={'spider': spider}
)
self.crawler.stats.inc_value(f"feedexport/success_count/{slot_type}")
def _start_new_batch(self, batch_id, uri, feed_options, spider, uri_template):
"""
Redirect the output data stream to a new file.

View File

@ -8,6 +8,7 @@ import tempfile
import warnings
from abc import ABC, abstractmethod
from collections import defaultdict
from contextlib import ExitStack
from io import BytesIO
from logging import getLogger
from pathlib import Path
@ -47,6 +48,21 @@ from scrapy.utils.test import (
)
from tests.mockserver import MockFTPServer, MockServer
from tests.spiders import ItemSpider
def path_to_url(path):
return urljoin('file:', pathname2url(str(path)))
def printf_escape(string):
return string.replace('%', '%%')
def build_url(path):
if path[0] != '/':
path = '/' + path
return urljoin('file:', path)
class FileFeedStorageTest(unittest.TestCase):
@ -620,12 +636,6 @@ class FeedExportTest(FeedExportTestBase):
def run_and_export(self, spider_cls, settings):
""" Run spider with specified settings; return exported data. """
def path_to_url(path):
return urljoin('file:', pathname2url(str(path)))
def printf_escape(string):
return string.replace('%', '%%')
FEEDS = settings.get('FEEDS') or {}
settings['FEEDS'] = {
printf_escape(path_to_url(file_path)): feed_options
@ -748,6 +758,69 @@ class FeedExportTest(FeedExportTestBase):
result = self._load_until_eof(data['marshal'], load_func=marshal.load)
self.assertEqual(expected, result)
@defer.inlineCallbacks
def test_stats_file_success(self):
settings = {
"FEEDS": {
printf_escape(path_to_url(self._random_temp_filename())): {
"format": "json",
}
},
}
crawler = get_crawler(ItemSpider, settings)
with MockServer() as mockserver:
yield crawler.crawl(mockserver=mockserver)
self.assertIn("feedexport/success_count/FileFeedStorage", crawler.stats.get_stats())
self.assertEqual(crawler.stats.get_value("feedexport/success_count/FileFeedStorage"), 1)
@defer.inlineCallbacks
def test_stats_file_failed(self):
settings = {
"FEEDS": {
printf_escape(path_to_url(self._random_temp_filename())): {
"format": "json",
}
},
}
crawler = get_crawler(ItemSpider, settings)
with ExitStack() as stack:
mockserver = stack.enter_context(MockServer())
stack.enter_context(
mock.patch(
"scrapy.extensions.feedexport.FileFeedStorage.store",
side_effect=KeyError("foo"))
)
yield crawler.crawl(mockserver=mockserver)
self.assertIn("feedexport/failed_count/FileFeedStorage", crawler.stats.get_stats())
self.assertEqual(crawler.stats.get_value("feedexport/failed_count/FileFeedStorage"), 1)
@defer.inlineCallbacks
def test_stats_multiple_file(self):
settings = {
'AWS_ACCESS_KEY_ID': 'access_key',
'AWS_SECRET_ACCESS_KEY': 'secret_key',
"FEEDS": {
printf_escape(path_to_url(self._random_temp_filename())): {
"format": "json",
},
"s3://bucket/key/foo.csv": {
"format": "csv",
},
"stdout:": {
"format": "xml",
}
},
}
crawler = get_crawler(ItemSpider, settings)
with MockServer() as mockserver, mock.patch.object(S3FeedStorage, "store"):
yield crawler.crawl(mockserver=mockserver)
self.assertIn("feedexport/success_count/FileFeedStorage", crawler.stats.get_stats())
self.assertIn("feedexport/success_count/S3FeedStorage", crawler.stats.get_stats())
self.assertIn("feedexport/success_count/StdoutFeedStorage", crawler.stats.get_stats())
self.assertEqual(crawler.stats.get_value("feedexport/success_count/FileFeedStorage"), 1)
self.assertEqual(crawler.stats.get_value("feedexport/success_count/S3FeedStorage"), 1)
self.assertEqual(crawler.stats.get_value("feedexport/success_count/StdoutFeedStorage"), 1)
@defer.inlineCallbacks
def test_export_items(self):
# feed exporters use field names from Item
@ -1256,11 +1329,6 @@ class BatchDeliveriesTest(FeedExportTestBase):
def run_and_export(self, spider_cls, settings):
""" Run spider with specified settings; return exported data. """
def build_url(path):
if path[0] != '/':
path = '/' + path
return urljoin('file:', path)
FEEDS = settings.get('FEEDS') or {}
settings['FEEDS'] = {
build_url(file_path): feed
@ -1550,6 +1618,22 @@ class BatchDeliveriesTest(FeedExportTestBase):
data = yield self.exported_data(items, settings)
self.assertEqual(len(items) + 1, len(data['json']))
@defer.inlineCallbacks
def test_stats_batch_file_success(self):
settings = {
"FEEDS": {
build_url(os.path.join(self._random_temp_filename(), "json", self._file_mark)): {
"format": "json",
}
},
"FEED_EXPORT_BATCH_ITEM_COUNT": 1,
}
crawler = get_crawler(ItemSpider, settings)
with MockServer() as mockserver:
yield crawler.crawl(total=2, mockserver=mockserver)
self.assertIn("feedexport/success_count/FileFeedStorage", crawler.stats.get_stats())
self.assertEqual(crawler.stats.get_value("feedexport/success_count/FileFeedStorage"), 12)
@defer.inlineCallbacks
def test_s3_export(self):
skip_if_no_boto()