Repository: fatiando/pooch

Issue:
Use logging instead of warnings for user feedback
**Description of the desired feature**

Pooch currently uses warnings to give feedback to users when a file is being downloaded, an archive unpacked, etc. The logging module from the Python standard library is a better fit for this purpose.

* More easily adjust how important the feedback is -- downloading a file is information, not having permission to create a destination directory is a warning or error
* Users can choose to silence feedback from pooch without silencing warnings from other libraries

**Are you willing to help implement and maintain this feature?** Yes

Discussion:
danshapero: It looks like the tests are failing on Windows but not on Mac or Linux; I don't have a Windows machine to test on. I think there might be some difference in how temporary files work. Any suggestions?
leouieda: @danshapero yep, we had a lot of failures before on Windows because of temporary file management. Be sure to use `with` blocks for your temp files so they are always closed and cleaned up. That usually helps.

I also see that there are a lot of changes in this PR. It might be best to focus on replacing warnings with logs in a single place so we can decide on an API. After that, you can propagate the changes to other parts of the code. That will make this a bit easier to review. Sorry for not specifying this earlier.

danshapero: You're right, this was a huge change, and what with other recent improvements that you made it was difficult for me to rebase from master. I've reduced it down to a smaller change so it'll be easier to review and iterate on. I've added a logging event to the function `pooch.core.Pooch.fetch` and a test that the log is written properly to the `test_pooch_custom_url`.
danshapero: I managed to fix the permission problem on Windows by logging to memory using [StringIO](https://docs.python.org/3/library/io.html#io.StringIO) rather than to a file on disk.
leouieda: @danshapero this looks great to me! I think it's safe to go ahead and make the full transition from warnings to logs. 
danshapero: @leouieda This is ready for another look. The test that nothing gets logged when you set the level to critical is a little weird -- pytest also captures logging events, so setting the log level within a test doesn't do what it normally does.
leouieda: @danshapero this looks great! Could you add a little section in `doc/usage.rst` with an example of silencing Pooch in case someone doesn't want the verbosity? After that, we should be good to go.
leouieda: Might be worth merging in `master` before continuing.
danshapero: I wrote enough documentation to explain how to silence the logging events, but I can change it as you see fit.
leouieda: Awesome @danshapero! Looks great to me. Merging!

Patch:
diff --git a/doc/api/index.rst b/doc/api/index.rst
index 24e7b17..4b61738 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -26,6 +26,7 @@ Utilities
     make_registry
     file_hash
     check_version
+    get_logger
 
 Downloaders
 -----------
diff --git a/doc/usage.rst b/doc/usage.rst
index ea387f6..2482493 100644
--- a/doc/usage.rst
+++ b/doc/usage.rst
@@ -493,6 +493,24 @@ this:
     ``tqdm`` is not installed by default with Pooch. You will have to install it
     separately in order to use this feature.
 
+
+Adjusting the logging level
+---------------------------
+
+Pooch will log events like downloading a new file, updating an existing one, or
+unpacking an archive by printing to the terminal. You can change how verbose these
+events are by getting the event logger from pooch and changing the logging level:
+
+.. code:: python
+
+    logger = pooch.get_logger()
+    logger.setLevel("WARNING")
+
+Most of the events from Pooch are logged at the info level; this code says that you only
+care about warnings or errors, like inability to create the data cache. The event logger
+is a :class:`logging.Logger` object, so you can use that class's methods to handle
+logging events in more sophisticated ways if you wish.
+
 So you have 1000 data files
 ---------------------------
 
diff --git a/pooch/__init__.py b/pooch/__init__.py
index c6d9495..258fc2b 100644
--- a/pooch/__init__.py
+++ b/pooch/__init__.py
@@ -2,7 +2,7 @@
 # Import functions/classes to make the API
 from . import version
 from .core import Pooch, create
-from .utils import os_cache, file_hash, make_registry, check_version
+from .utils import os_cache, file_hash, make_registry, check_version, get_logger
 from .downloaders import HTTPDownloader, FTPDownloader
 from .processors import Unzip, Untar, Decompress
 
diff --git a/pooch/core.py b/pooch/core.py
index a099438..e205b67 100644
--- a/pooch/core.py
+++ b/pooch/core.py
@@ -6,11 +6,10 @@ import os
 from pathlib import Path
 import shutil
 import tempfile
-from warnings import warn
 import ftplib
 
 import requests
-from .utils import file_hash, check_version, parse_url
+from .utils import file_hash, check_version, parse_url, get_logger
 from .downloaders import HTTPDownloader, FTPDownloader
 
 KNOWN_DOWNLOADERS = {
@@ -172,17 +171,15 @@ def create(
             tempfile.NamedTemporaryFile(dir=path)
     except PermissionError:
         message = (
-            "Cannot write to data cache '{}'. "
-            "Will not be able to download remote data files. ".format(path)
+            "Cannot write to data cache '%s'. "
+            "Will not be able to download remote data files. "
         )
+        args = [path]
         if env is not None:
-            message = (
-                message
-                + "Use environment variable '{}' to specify another directory.".format(
-                    env
-                )
-            )
-        warn(message)
+            message += "Use environment variable '%s' to specify another directory."
+            args += [env]
+
+        get_logger().warning(message, *args)
     pup = Pooch(path=Path(path), base_url=base_url, registry=registry, urls=urls)
     return pup
 
@@ -374,10 +371,12 @@ class Pooch:
 
         if action in ("download", "update"):
             action_word = dict(download="Downloading", update="Updating")
-            warn(
-                "{} data file '{}' from remote data store '{}' to '{}'.".format(
-                    action_word[action], fname, self.get_url(fname), str(self.path)
-                )
+            get_logger().info(
+                "%s data file '%s' from remote data store '%s' to '%s'.",
+                action_word[action],
+                fname,
+                self.get_url(fname),
+                str(self.path),
             )
 
             parsed_url = parse_url(url)
diff --git a/pooch/processors.py b/pooch/processors.py
index 385aa80..f4fd49b 100644
--- a/pooch/processors.py
+++ b/pooch/processors.py
@@ -9,7 +9,8 @@ import lzma
 import shutil
 from zipfile import ZipFile
 from tarfile import TarFile
-from warnings import warn
+
+from .utils import get_logger
 
 
 class ExtractorProcessor:  # pylint: disable=too-few-public-methods
@@ -115,15 +116,15 @@ class Unzip(ExtractorProcessor):  # pylint: disable=too-few-public-methods
         """
         with ZipFile(fname, "r") as zip_file:
             if self.members is None:
-                warn("Unzipping contents of '{}' to '{}'".format(fname, extract_dir))
+                get_logger().info(
+                    "Unzipping contents of '%s' to '%s'", fname, extract_dir
+                )
                 # Unpack all files from the archive into our new folder
                 zip_file.extractall(path=extract_dir)
             else:
                 for member in self.members:
-                    warn(
-                        "Extracting '{}' from '{}' to '{}'".format(
-                            member, fname, extract_dir
-                        )
+                    get_logger().info(
+                        "Extracting '%s' from '%s' to '%s'", member, fname, extract_dir
                     )
                     # Extract the data file from within the archive
                     with zip_file.open(member) as data_file:
@@ -160,15 +161,15 @@ class Untar(ExtractorProcessor):  # pylint: disable=too-few-public-methods
         """
         with TarFile.open(fname, "r") as tar_file:
             if self.members is None:
-                warn("Untarring contents of '{}' to '{}'".format(fname, extract_dir))
+                get_logger().info(
+                    "Untarring contents of '%s' to '%s'", fname, extract_dir
+                )
                 # Unpack all files from the archive into our new folder
                 tar_file.extractall(path=extract_dir)
             else:
                 for member in self.members:
-                    warn(
-                        "Extracting '{}' from '{}' to '{}'".format(
-                            member, fname, extract_dir
-                        )
+                    get_logger().info(
+                        "Extracting '%s' from '%s' to '%s'", member, fname, extract_dir
                     )
                     # Extract the data file from within the archive
                     # Python 2.7: extractfile doesn't return a context manager
@@ -238,10 +239,11 @@ class Decompress:  # pylint: disable=too-few-public-methods
         """
         decompressed = fname + ".decomp"
         if action in ("update", "download") or not os.path.exists(decompressed):
-            warn(
-                "Decompressing '{}' to '{}' using method '{}'.".format(
-                    fname, decompressed, self.method
-                )
+            get_logger().info(
+                "Decompressing '%s' to '%s' using method '%s'.",
+                fname,
+                decompressed,
+                self.method,
             )
             module = self._compression_module(fname)
             with open(decompressed, "w+b") as output:
diff --git a/pooch/utils.py b/pooch/utils.py
index f3e0ef7..0381fbb 100644
--- a/pooch/utils.py
+++ b/pooch/utils.py
@@ -1,6 +1,7 @@
 """
 Misc utilities
 """
+import logging
 from pathlib import Path
 import hashlib
 from urllib.parse import urlsplit
@@ -8,6 +9,26 @@ import appdirs
 from packaging.version import Version
 
 
+LOGGER = logging.Logger("pooch")
+LOGGER.addHandler(logging.StreamHandler())
+
+
+def get_logger():
+    r"""
+    Get the default event logger.
+
+    The logger records events like downloading files, unzipping archives, etc.
+    Use the method :meth:`logging.Logger.setLevel` of this object to adjust the
+    verbosity level from Pooch.
+
+    Returns
+    -------
+    logger : :class:`logging.Logger`
+        The logger object for Pooch
+    """
+    return LOGGER
+
+
 def os_cache(project):
     r"""
     Default cache location based on the operating system.


Test patch:
diff --git a/pooch/tests/test_core.py b/pooch/tests/test_core.py
index d81e96a..f4da1b9 100644
--- a/pooch/tests/test_core.py
+++ b/pooch/tests/test_core.py
@@ -6,7 +6,6 @@ import sys
 from pathlib import Path
 from tempfile import TemporaryDirectory
 import tempfile
-import warnings
 
 import pytest
 
@@ -16,7 +15,7 @@ except ImportError:
     tqdm = None
 
 from .. import Pooch, create
-from ..utils import file_hash
+from ..utils import file_hash, get_logger
 from ..downloaders import HTTPDownloader, FTPDownloader
 
 from .utils import (
@@ -24,6 +23,7 @@ from .utils import (
     pooch_test_registry,
     check_tiny_data,
     check_large_data,
+    capture_log,
 )
 
 # FTP doesn't work on Travis CI so need to be able to skip tests there
@@ -53,18 +53,17 @@ def test_pooch_custom_url():
         urls = {"tiny-data.txt": BASEURL + "tiny-data.txt"}
         # Setup a pooch in a temp dir
         pup = Pooch(path=path, base_url="", registry=REGISTRY, urls=urls)
-        # Check that the warning says that the file is being updated
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that the logs say that the file is being downloaded
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt")
-            assert len(warn) == 1
-            assert issubclass(warn[-1].category, UserWarning)
-            assert str(warn[-1].message).split()[0] == "Downloading"
-            assert str(warn[-1].message).split()[-1] == "'{}'.".format(path)
+            logs = log_file.getvalue()
+            assert logs.split()[0] == "Downloading"
+            assert logs.split()[-1] == "'{}'.".format(path)
         check_tiny_data(fname)
-        # Check that no warnings happen when not downloading
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that no logging happens when there are no events
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt")
-            assert not warn
+            assert log_file.getvalue() == ""
 
 
 def test_pooch_download():
@@ -74,21 +73,34 @@ def test_pooch_download():
         true_path = str(path / "tiny-data.txt")
         # Setup a pooch in a temp dir
         pup = Pooch(path=path, base_url=BASEURL, registry=REGISTRY)
-        # Check that the warning says that the file is being downloaded
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that the logs say that the file is being downloaded
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt")
-            assert len(warn) == 1
-            assert issubclass(warn[-1].category, UserWarning)
-            assert str(warn[-1].message).split()[0] == "Downloading"
-            assert str(warn[-1].message).split()[-1] == "'{}'.".format(path)
+            logs = log_file.getvalue()
+            assert logs.split()[0] == "Downloading"
+            assert logs.split()[-1] == "'{}'.".format(path)
         # Check that the downloaded file has the right content
         assert true_path == fname
         check_tiny_data(fname)
         assert file_hash(fname) == REGISTRY["tiny-data.txt"]
-        # Check that no warnings happen when not downloading
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that no logging happens when not downloading
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt")
-            assert not warn
+            assert log_file.getvalue() == ""
+
+
+def test_pooch_logging_level():
+    "Setup a pooch and check that no logging happens when the level is raised"
+    with TemporaryDirectory() as local_store:
+        path = Path(local_store)
+        urls = {"tiny-data.txt": BASEURL + "tiny-data.txt"}
+        # Setup a pooch in a temp dir
+        pup = Pooch(path=path, base_url="", registry=REGISTRY, urls=urls)
+        # Capture only critical logging events
+        with capture_log("CRITICAL") as log_file:
+            fname = pup.fetch("tiny-data.txt")
+            assert log_file.getvalue() == ""
+        check_tiny_data(fname)
 
 
 def test_pooch_update():
@@ -102,21 +114,20 @@ def test_pooch_update():
             fin.write("different data")
         # Setup a pooch in a temp dir
         pup = Pooch(path=path, base_url=BASEURL, registry=REGISTRY)
-        # Check that the warning says that the file is being updated
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that the logs say that the file is being updated
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt")
-            assert len(warn) == 1
-            assert issubclass(warn[-1].category, UserWarning)
-            assert str(warn[-1].message).split()[0] == "Updating"
-            assert str(warn[-1].message).split()[-1] == "'{}'.".format(path)
+            logs = log_file.getvalue()
+            assert logs.split()[0] == "Updating"
+            assert logs.split()[-1] == "'{}'.".format(path)
         # Check that the updated file has the right content
         assert true_path == fname
         check_tiny_data(fname)
         assert file_hash(fname) == REGISTRY["tiny-data.txt"]
-        # Check that no warnings happen when not downloading
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that no logging happens when not downloading
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt")
-            assert not warn
+            assert log_file.getvalue() == ""
 
 
 def test_pooch_corrupted():
@@ -125,22 +136,20 @@ def test_pooch_corrupted():
     with TemporaryDirectory() as local_store:
         path = os.path.abspath(local_store)
         pup = Pooch(path=path, base_url=BASEURL, registry=REGISTRY_CORRUPTED)
-        with warnings.catch_warnings(record=True) as warn:
+        with capture_log() as log_file:
             with pytest.raises(ValueError):
                 pup.fetch("tiny-data.txt")
-            assert len(warn) == 1
-            assert issubclass(warn[-1].category, UserWarning)
-            assert str(warn[-1].message).split()[0] == "Downloading"
-            assert str(warn[-1].message).split()[-1] == "'{}'.".format(path)
+            logs = log_file.getvalue()
+            assert logs.split()[0] == "Downloading"
+            assert logs.split()[-1] == "'{}'.".format(path)
     # and the case where the file exists but hash doesn't match
     pup = Pooch(path=DATA_DIR, base_url=BASEURL, registry=REGISTRY_CORRUPTED)
-    with warnings.catch_warnings(record=True) as warn:
+    with capture_log() as log_file:
         with pytest.raises(ValueError):
             pup.fetch("tiny-data.txt")
-        assert len(warn) == 1
-        assert issubclass(warn[-1].category, UserWarning)
-        assert str(warn[-1].message).split()[0] == "Updating"
-        assert str(warn[-1].message).split()[-1] == "'{}'.".format(DATA_DIR)
+        logs = log_file.getvalue()
+        assert logs.split()[0] == "Updating"
+        assert logs.split()[-1] == "'{}'.".format(DATA_DIR)
 
 
 def test_pooch_file_not_in_registry():
@@ -206,7 +215,7 @@ def test_create_makedirs_permissionerror(monkeypatch):
 
     monkeypatch.setattr(os, "makedirs", mockmakedirs)
 
-    with warnings.catch_warnings(record=True) as warn:
+    with capture_log() as log_file:
         pup = create(
             path=data_cache,
             base_url="",
@@ -215,10 +224,9 @@ def test_create_makedirs_permissionerror(monkeypatch):
             env="SOME_VARIABLE",
             registry={"afile.txt": "ahash"},
         )
-        assert len(warn) == 1
-        assert issubclass(warn[-1].category, UserWarning)
-        assert str(warn[-1].message).startswith("Cannot write to data cache")
-        assert "'SOME_VARIABLE'" in str(warn[-1].message)
+        logs = log_file.getvalue()
+        assert logs.startswith("Cannot write to data cache")
+        assert "'SOME_VARIABLE'" in logs
 
     with pytest.raises(PermissionError):
         pup.fetch("afile.txt")
@@ -239,7 +247,7 @@ def test_create_newfile_permissionerror(monkeypatch):
 
         monkeypatch.setattr(tempfile, "NamedTemporaryFile", mocktempfile)
 
-        with warnings.catch_warnings(record=True) as warn:
+        with capture_log() as log_file:
             pup = create(
                 path=data_cache,
                 base_url="ftp://random.ftp.com/",
@@ -248,13 +256,12 @@ def test_create_newfile_permissionerror(monkeypatch):
                 env="SOME_VARIABLE",
                 registry={"afile.txt": "ahash"},
             )
-            assert len(warn) == 1
-            assert issubclass(warn[-1].category, UserWarning)
-            assert str(warn[-1].message).startswith("Cannot write to data cache")
-            assert "'SOME_VARIABLE'" in str(warn[-1].message)
+            logs = log_file.getvalue()
+            assert logs.startswith("Cannot write to data cache")
+            assert "'SOME_VARIABLE'" in logs
 
-            with pytest.raises(PermissionError):
-                pup.fetch("afile.txt")
+        with pytest.raises(PermissionError):
+            pup.fetch("afile.txt")
 
 
 def test_unsupported_protocol():
@@ -310,28 +317,29 @@ def test_downloader(capsys):
 
     def download(url, output_file, pup):  # pylint: disable=unused-argument
         "Download through HTTP and warn that we're doing it"
-        warnings.warn("downloader executed")
+        get_logger().info("downloader executed")
         HTTPDownloader()(url, output_file, pup)
 
     with TemporaryDirectory() as local_store:
         path = Path(local_store)
         # Setup a pooch in a temp dir
         pup = Pooch(path=path, base_url=BASEURL, registry=REGISTRY)
-        # Check that the warning says that the file is being downloaded
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that the logs say that the file is being downloaded
+        with capture_log() as log_file:
             fname = pup.fetch("large-data.txt", downloader=download)
-            assert len(warn) == 2
-            assert all(issubclass(w.category, UserWarning) for w in warn)
-            assert str(warn[-2].message).split()[0] == "Downloading"
-            assert str(warn[-1].message) == "downloader executed"
+            logs = log_file.getvalue()
+            lines = logs.splitlines()
+            assert len(lines) == 2
+            assert lines[0].split()[0] == "Downloading"
+            assert lines[1] == "downloader executed"
         # Read stderr and make sure no progress bar was printed by default
         assert not capsys.readouterr().err
         # Check that the downloaded file has the right content
         check_large_data(fname)
-        # Check that no warnings happen when not downloading
-        with warnings.catch_warnings(record=True) as warn:
+        # Check that no logging happens when not downloading
+        with capture_log() as log_file:
             fname = pup.fetch("large-data.txt")
-            assert not warn
+            assert log_file.getvalue() == ""
 
 
 @pytest.mark.skipif(tqdm is not None, reason="tqdm must be missing")
diff --git a/pooch/tests/test_integration.py b/pooch/tests/test_integration.py
index d7dd16f..7bfbc13 100644
--- a/pooch/tests/test_integration.py
+++ b/pooch/tests/test_integration.py
@@ -5,13 +5,12 @@ Test the entire process of creating a Pooch and using it.
 import os
 import shutil
 from pathlib import Path
-import warnings
 
 import pytest
 
 from .. import create, os_cache
 from ..version import full_version
-from .utils import check_tiny_data
+from .utils import check_tiny_data, capture_log
 
 
 @pytest.fixture
@@ -37,18 +36,14 @@ def test_fetch(pup):
     # Make sure the storage has been cleaned up before running the tests
     assert not pup.abspath.exists()
     for target in ["tiny-data.txt", "subdir/tiny-data.txt"]:
-        with warnings.catch_warnings(record=True) as warn:
+        with capture_log() as log_file:
             fname = pup.fetch(target)
-            assert len(warn) == 1
-            assert issubclass(warn[-1].category, UserWarning)
-            assert str(warn[-1].message).split()[0] == "Downloading"
+            assert log_file.getvalue().split()[0] == "Downloading"
         check_tiny_data(fname)
         # Now modify the file to trigger an update on the next fetch
         with open(fname, "w") as fin:
             fin.write("The data is now different")
-        with warnings.catch_warnings(record=True) as warn:
+        with capture_log() as log_file:
             fname = pup.fetch(target)
-            assert len(warn) == 1
-            assert issubclass(warn[-1].category, UserWarning)
-            assert str(warn[-1].message).split()[0] == "Updating"
+            assert log_file.getvalue().split()[0] == "Updating"
         check_tiny_data(fname)
diff --git a/pooch/tests/test_processors.py b/pooch/tests/test_processors.py
index f750656..b47f9d4 100644
--- a/pooch/tests/test_processors.py
+++ b/pooch/tests/test_processors.py
@@ -10,7 +10,7 @@ import pytest
 from .. import Pooch
 from ..processors import Unzip, Untar, ExtractorProcessor, Decompress
 
-from .utils import pooch_test_url, pooch_test_registry, check_tiny_data
+from .utils import pooch_test_url, pooch_test_registry, check_tiny_data, capture_log
 
 
 REGISTRY = pooch_test_registry()
@@ -30,20 +30,21 @@ def test_decompress(method, ext):
         true_path = str(path / ".".join(["tiny-data.txt", ext, "decomp"]))
         # Setup a pooch in a temp dir
         pup = Pooch(path=path, base_url=BASEURL, registry=REGISTRY)
-        # Check the warnings when downloading and from the processor
-        with warnings.catch_warnings(record=True) as warn:
+        # Check the logs when downloading and from the processor
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt." + ext, processor=processor)
-            assert len(warn) == 2
-            assert all(issubclass(w.category, UserWarning) for w in warn)
-            assert str(warn[-2].message).split()[0] == "Downloading"
-            assert str(warn[-1].message).startswith("Decompressing")
-            assert method in str(warn[-1].message)
+            logs = log_file.getvalue()
+            lines = logs.splitlines()
+            assert len(lines) == 2
+            assert lines[0].split()[0] == "Downloading"
+            assert lines[1].startswith("Decompressing")
+            assert method in lines[1]
         assert fname == true_path
         check_tiny_data(fname)
         # Check that processor doesn't execute when not downloading
-        with warnings.catch_warnings(record=True) as warn:
+        with capture_log() as log_file:
             fname = pup.fetch("tiny-data.txt." + ext, processor=processor)
-            assert not warn
+            assert log_file.getvalue() == ""
         assert fname == true_path
         check_tiny_data(fname)
 
@@ -92,23 +93,25 @@ def test_processors(proc_cls, ext):
         true_path = str(path / extract_dir / "tiny-data.txt")
         # Setup a pooch in a temp dir
         pup = Pooch(path=path, base_url=BASEURL, registry=REGISTRY)
-        # Check the warnings when downloading and from the processor
-        with warnings.catch_warnings(record=True) as warn:
+        # Check the logs when downloading and from the processor
+        with capture_log() as log_file:
             fnames = pup.fetch("tiny-data" + ext, processor=processor)
             fname = fnames[0]
             assert len(fnames) == 1
-            assert len(warn) == 2
-            assert all(issubclass(w.category, UserWarning) for w in warn)
-            assert str(warn[-2].message).split()[0] == "Downloading"
-            assert str(warn[-1].message).startswith("Extracting 'tiny-data.txt'")
+            logs = log_file.getvalue()
+            lines = logs.splitlines()
+            assert len(lines) == 2
+            assert lines[0].split()[0] == "Downloading"
+            assert lines[1].startswith("Extracting 'tiny-data.txt'")
+
         assert fname == true_path
         check_tiny_data(fname)
         # Check that processor doesn't execute when not downloading
-        with warnings.catch_warnings(record=True) as warn:
+        with capture_log() as log_file:
             fnames = pup.fetch("tiny-data" + ext, processor=processor)
             fname = fnames[0]
             assert len(fnames) == 1
-            assert not warn
+            assert log_file.getvalue() == ""
         assert fname == true_path
         check_tiny_data(fname)
 
@@ -131,21 +134,22 @@ def test_processor_multiplefiles(proc_cls, ext, msg):
         }
         # Setup a pooch in a temp dir
         pup = Pooch(path=path, base_url=BASEURL, registry=REGISTRY)
-        # Check the warnings when downloading and from the processor
-        with warnings.catch_warnings(record=True) as warn:
+        # Check the logs when downloading and from the processor
+        with capture_log() as log_file:
             fnames = pup.fetch("store" + ext, processor=processor)
-            assert len(warn) == 2
-            assert all(issubclass(w.category, UserWarning) for w in warn)
-            assert str(warn[-2].message).split()[0] == "Downloading"
-            assert str(warn[-1].message).startswith("{} contents".format(msg))
+            logs = log_file.getvalue()
+            lines = logs.splitlines()
+            assert len(lines) == 2
+            assert lines[0].split()[0] == "Downloading"
+            assert lines[1].startswith("{} contents".format(msg))
             assert len(fnames) == 2
             assert true_paths == set(fnames)
             for fname in fnames:
                 check_tiny_data(fname)
         # Check that processor doesn't execute when not downloading
-        with warnings.catch_warnings(record=True) as warn:
+        with capture_log() as log_file:
             fnames = pup.fetch("store" + ext, processor=processor)
-            assert not warn
+            assert log_file.getvalue() == ""
             assert len(fnames) == 2
             assert true_paths == set(fnames)
             for fname in fnames:
diff --git a/pooch/tests/utils.py b/pooch/tests/utils.py
index f090789..d87ebbe 100644
--- a/pooch/tests/utils.py
+++ b/pooch/tests/utils.py
@@ -2,9 +2,12 @@
 Utilities for testing code.
 """
 import os
+import io
+import logging
+from contextlib import contextmanager
 
 from ..version import full_version
-from ..utils import check_version
+from ..utils import check_version, get_logger
 
 
 def check_tiny_data(fname):
@@ -75,3 +78,21 @@ def pooch_test_registry():
         "tiny-data.txt.xz": "99dcb5c32a6e916344bacb4badcbc2f2b6ee196977d1d8187610c21e7e607765",
     }
     return registry
+
+
+@contextmanager
+def capture_log(level=logging.DEBUG):
+    """
+    Create a context manager for reading from the logs.
+
+    Yields
+    ------
+    log_file : StringIO
+        a file-like object to which the logs were written
+    """
+    log_file = io.StringIO()
+    handler = logging.StreamHandler(log_file)
+    handler.setLevel(level)
+    get_logger().addHandler(handler)
+    yield log_file
+    get_logger().removeHandler(handler)