mirror of https://github.com/scrapy/scrapy.git
Add failed and success count stats to feedstorage backends (#4850)
This commit is contained in:
parent
c20b34269f
commit
85604e1078
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue