mirror of https://github.com/scrapy/scrapy.git
Merge 47f654e746 into e28e56aa61
This commit is contained in:
commit
64c89bae1b
|
|
@ -387,12 +387,13 @@ is ``True``.
|
|||
Logging settings
|
||||
----------------
|
||||
|
||||
**Logging settings** are settings that configure the global root logging
|
||||
handler installed by :func:`~scrapy.utils.log.configure_logging`.
|
||||
**Logging settings** are settings that configure logging process-wide, mostly
|
||||
through the global root logging handler installed by
|
||||
:func:`~scrapy.utils.log.configure_logging`.
|
||||
|
||||
These settings can be defined from a spider. However, because only 1 root
|
||||
logging handler is active per process, these settings cannot use a different
|
||||
value per spider when :ref:`running multiple spiders in the same process
|
||||
These settings can be defined from a spider. However, because logging
|
||||
configuration is global, these settings cannot use a different value per
|
||||
spider when :ref:`running multiple spiders in the same process
|
||||
<run-multiple-spiders>`.
|
||||
|
||||
These settings are:
|
||||
|
|
@ -404,6 +405,7 @@ These settings are:
|
|||
- :setting:`LOG_FILE_APPEND`
|
||||
- :setting:`LOG_FORMAT`
|
||||
- :setting:`LOG_LEVEL`
|
||||
- :setting:`LOG_LEVELS`
|
||||
- :setting:`LOG_SHORT_NAMES`
|
||||
- :setting:`LOG_STDOUT`
|
||||
|
||||
|
|
@ -1670,6 +1672,30 @@ INFO, DEBUG. For more info see :ref:`topics-logging`.
|
|||
|
||||
.. note:: This is a :ref:`logging setting <logging-settings>`.
|
||||
|
||||
.. setting:: LOG_LEVELS
|
||||
|
||||
LOG_LEVELS
|
||||
----------
|
||||
|
||||
Default: ``{}``
|
||||
|
||||
.. versionadded:: VERSION
|
||||
|
||||
Minimum level to log for specific loggers.
|
||||
|
||||
It takes precedence over the levels that Scrapy sets by default::
|
||||
|
||||
{
|
||||
"filelock": "ERROR",
|
||||
"hpack": "ERROR",
|
||||
"httpcore": "ERROR",
|
||||
"httpx": "WARNING",
|
||||
"scrapy": "DEBUG",
|
||||
"twisted": "ERROR",
|
||||
}
|
||||
|
||||
.. note:: This is a :ref:`logging setting <logging-settings>`.
|
||||
|
||||
.. setting:: LOG_STDOUT
|
||||
|
||||
LOG_STDOUT
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from scrapy.signalmanager import SignalManager
|
|||
from scrapy.spiderloader import SpiderLoaderProtocol, get_spider_loader
|
||||
from scrapy.utils.defer import deferred_from_coro
|
||||
from scrapy.utils.log import (
|
||||
_configure_logger_levels,
|
||||
configure_logging,
|
||||
get_scrapy_root_handler,
|
||||
install_scrapy_root_handler,
|
||||
|
|
@ -122,7 +123,7 @@ class Crawler:
|
|||
self.spidercls: type[Spider] = spidercls
|
||||
self.settings: Settings = settings.copy()
|
||||
self.spidercls.update_settings(self.settings)
|
||||
self._update_root_log_handler()
|
||||
self._update_logging()
|
||||
|
||||
self.addons: AddonManager = AddonManager(self)
|
||||
self.signals: SignalManager = SignalManager(self)
|
||||
|
|
@ -139,9 +140,10 @@ class Crawler:
|
|||
self._request_fingerprinter: RequestFingerprinterProtocol | None = None
|
||||
self._stats: StatsCollector | None = None
|
||||
|
||||
def _update_root_log_handler(self) -> None:
|
||||
def _update_logging(self) -> None:
|
||||
if get_scrapy_root_handler() is not None:
|
||||
# scrapy root handler already installed: update it with new settings
|
||||
_configure_logger_levels(self.settings)
|
||||
install_scrapy_root_handler(self.settings)
|
||||
|
||||
def _apply_settings(self) -> None:
|
||||
|
|
@ -268,7 +270,7 @@ class Crawler:
|
|||
try:
|
||||
self.spider = self._create_spider(*args, **kwargs)
|
||||
self._apply_settings()
|
||||
self._update_root_log_handler()
|
||||
self._update_logging()
|
||||
self.engine = self._create_engine()
|
||||
try:
|
||||
yield deferred_from_coro(self.engine.open_spider_async())
|
||||
|
|
@ -302,7 +304,7 @@ class Crawler:
|
|||
try:
|
||||
self.spider = self._create_spider(*args, **kwargs)
|
||||
self._apply_settings()
|
||||
self._update_root_log_handler()
|
||||
self._update_logging()
|
||||
self.engine = self._create_engine()
|
||||
try:
|
||||
await self.engine.open_spider_async()
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ __all__ = [
|
|||
"LOG_FORMAT",
|
||||
"LOG_FORMATTER",
|
||||
"LOG_LEVEL",
|
||||
"LOG_LEVELS",
|
||||
"LOG_SHORT_NAMES",
|
||||
"LOG_STDOUT",
|
||||
"LOG_VERSIONS",
|
||||
|
|
@ -449,6 +450,7 @@ LOG_FILE_APPEND = True
|
|||
LOG_FORMAT = "%(asctime)s [%(name)s] %(levelname)s: %(message)s"
|
||||
LOG_FORMATTER = "scrapy.logformatter.LogFormatter"
|
||||
LOG_LEVEL = "DEBUG"
|
||||
LOG_LEVELS: dict[str, str] = {}
|
||||
LOG_SHORT_NAMES = False
|
||||
LOG_STDOUT = False
|
||||
LOG_VERSIONS = [
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class TopLevelFormatter(logging.Filter):
|
|||
return True
|
||||
|
||||
|
||||
DEFAULT_LOGGING = {
|
||||
DEFAULT_LOGGING: dict[str, Any] = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"loggers": {
|
||||
|
|
@ -109,7 +109,8 @@ def configure_logging(
|
|||
This function does:
|
||||
|
||||
- Route warnings and twisted logging through Python standard logging
|
||||
- Assign DEBUG and ERROR level to Scrapy and Twisted loggers respectively
|
||||
- Set log levels for the Scrapy, Twisted and third-party loggers, which
|
||||
:setting:`LOG_LEVELS` can override
|
||||
- Route stdout to log if LOG_STDOUT setting is True
|
||||
|
||||
When ``install_root_handler`` is True (default), this function also
|
||||
|
|
@ -125,11 +126,11 @@ def configure_logging(
|
|||
observer = twisted_log.PythonLoggingObserver("twisted")
|
||||
observer.start()
|
||||
|
||||
dictConfig(DEFAULT_LOGGING)
|
||||
|
||||
if isinstance(settings, dict) or settings is None:
|
||||
settings = Settings(settings)
|
||||
|
||||
_configure_logger_levels(settings)
|
||||
|
||||
if settings.getbool("LOG_STDOUT"):
|
||||
sys.stdout = StreamLogger(logging.getLogger("stdout"))
|
||||
|
||||
|
|
@ -137,6 +138,13 @@ def configure_logging(
|
|||
install_scrapy_root_handler(settings)
|
||||
|
||||
|
||||
def _configure_logger_levels(settings: Settings) -> None:
|
||||
loggers = DEFAULT_LOGGING["loggers"] | {
|
||||
name: {"level": level} for name, level in settings.getdict("LOG_LEVELS").items()
|
||||
}
|
||||
dictConfig(DEFAULT_LOGGING | {"loggers": loggers})
|
||||
|
||||
|
||||
_scrapy_root_handler: logging.Handler | None = None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -553,6 +553,20 @@ class TestCrawlerLogging:
|
|||
get_crawler(MySpider)
|
||||
assert get_scrapy_root_handler() is None
|
||||
|
||||
def test_spider_custom_settings_log_levels(self) -> None:
|
||||
class MySpider(scrapy.Spider):
|
||||
name = "spider"
|
||||
custom_settings = {"LOG_LEVELS": {"httpx": "DEBUG"}}
|
||||
|
||||
try:
|
||||
configure_logging()
|
||||
assert logging.getLogger("httpx").level == logging.WARNING
|
||||
get_crawler(MySpider)
|
||||
assert logging.getLogger("httpx").level == logging.DEBUG
|
||||
finally:
|
||||
_uninstall_scrapy_root_handler()
|
||||
logging.getLogger("httpx").setLevel(logging.NOTSET)
|
||||
|
||||
@coroutine_test
|
||||
async def test_spider_custom_settings_log_level(self, tmp_path: Path) -> None:
|
||||
log_file = Path(tmp_path, "log.txt")
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from scrapy.utils.log import (
|
|||
SpiderLoggerAdapter,
|
||||
StreamLogger,
|
||||
TopLevelFormatter,
|
||||
configure_logging,
|
||||
failure_to_exc_info,
|
||||
logformatter_adapter,
|
||||
)
|
||||
|
|
@ -160,6 +161,29 @@ def test_spider_logger_adapter_process(
|
|||
assert result_kwargs == expected_extra
|
||||
|
||||
|
||||
class TestLogLevels:
|
||||
@pytest.fixture(autouse=True)
|
||||
def restore_levels(self) -> Generator[None]:
|
||||
yield
|
||||
for name in ("httpx", "boto3"):
|
||||
logging.getLogger(name).setLevel(logging.NOTSET)
|
||||
|
||||
def test_defaults(self) -> None:
|
||||
configure_logging(install_root_handler=False)
|
||||
|
||||
assert logging.getLogger("httpx").level == logging.WARNING
|
||||
assert logging.getLogger("boto3").level == logging.NOTSET
|
||||
|
||||
def test_override(self) -> None:
|
||||
configure_logging(
|
||||
{"LOG_LEVELS": {"httpx": "DEBUG", "boto3": "ERROR"}},
|
||||
install_root_handler=False,
|
||||
)
|
||||
|
||||
assert logging.getLogger("httpx").level == logging.DEBUG
|
||||
assert logging.getLogger("boto3").level == logging.ERROR
|
||||
|
||||
|
||||
class TestLogging:
|
||||
@pytest.fixture
|
||||
def log_stream(self) -> StringIO:
|
||||
|
|
|
|||
Loading…
Reference in New Issue