Repository: hyperspy/rosettasciio (language: python)

Issue:
set_log_level for rosettasciio is needed
#### Describe the functionality you would like to see.
Like hyperspy's  hs.set_log_level function, rosettasciio.set_log_level is needed.
May be hs.set_log_level should also control rsciio.set_log_level.



Discussion / hints:
> Like hyperspy's hs.set_log_level function, rosettasciio.set_log_level is needed.

Yes, indeed it would be convenient to have something similiar.

> May be hs.set_log_level should also control rsciio.set_log_level.

In principle, it should stay separated.
> In principle, it should stay separated.

I agree it, but, currently no warning messages are shown while loading files from hyperspy.
Setting default logging level of WARNING seems useful.

Fix patch:
diff --git a/doc/api/index.rst b/doc/api/index.rst
index dbf5fb3f1..d7d6dbcb5 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -69,4 +69,8 @@ for reading/writing of data files:
   * `Kikuchipy <https://kikuchipy.org>`_: Processing, simulating and analyzing
     electron backscatter diffraction (EBSD) patterns in Python 
   * `PyXem <https://pyxem.readthedocs.io>`_: An open-source Python library for
-    multi-dimensional diffraction microscopy. 
+    multi-dimensional diffraction microscopy.
+  * `exSpy <https://hyperspy.org/exspy/>`_: Analysis of X-ray Energy Dispersive
+    Spectroscopy (EDS) and Electron Energy Loss Spectroscopy (EELS).
+  * `holospy <https://hyperspy.org/holospy/>`_: Analysis of (off-axis) electron
+    holography data.
diff --git a/doc/api/utils.rst b/doc/api/utils.rst
index f4a819652..23f62f47a 100644
--- a/doc/api/utils.rst
+++ b/doc/api/utils.rst
@@ -27,4 +27,8 @@ Distributed utility functions
 .. automodule:: rsciio.utils.distributed
    :members:
 
-"""
\ No newline at end of file
+Logging
+^^^^^^^
+
+.. automodule:: rsciio
+   :members: set_log_level
diff --git a/rsciio/__init__.py b/rsciio/__init__.py
index e15babbd2..9e99e5f7a 100644
--- a/rsciio/__init__.py
+++ b/rsciio/__init__.py
@@ -21,6 +21,11 @@
 from pathlib import Path
 import yaml
 
+from ._logger import set_log_level
+
+
+# Default to warning
+set_log_level("WARNING")
 
 IO_PLUGINS = []
 
@@ -59,10 +64,7 @@
             IO_PLUGINS.append(_specs)
 
 
-__all__ = [
-    "__version__",
-    "IO_PLUGINS",
-]
+__all__ = ["__version__", "IO_PLUGINS", "set_log_level"]
 
 
 def __dir__():
diff --git a/rsciio/_logger.py b/rsciio/_logger.py
new file mode 100644
index 000000000..a46721a75
--- /dev/null
+++ b/rsciio/_logger.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# Copyright 2007-2023 The HyperSpy developers
+#
+# This file is part of RosettaSciIO.
+#
+# RosettaSciIO is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# RosettaSciIO is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with RosettaSciIO. If not, see <https://www.gnu.org/licenses/#GPL>.
+
+import logging
+import sys
+
+
+def set_log_level(level):
+    """
+    Convenience function to set the log level of all rsciio modules.
+
+    Note: The log level of all other modules are left untouched.
+
+    Parameters
+    ----------
+    level : int or str
+        The log level to set. Any values that `logging.Logger.setLevel()`
+        accepts are valid. The default options are:
+
+        - 'CRITICAL'
+        - 'ERROR'
+        - 'WARNING'
+        - 'INFO'
+        - 'DEBUG'
+        - 'NOTSET'
+
+    Examples
+    --------
+    For normal logging of rsciio functions, you can set the log level like
+    this:
+
+    >>> import rsciio
+    >>> rsciio.set_log_level('INFO')
+    >>> from rsciio.digitalmicrograph import file_reader
+    >>> file_reader('my_file.dm3')
+    INFO:rsciio.digital_micrograph:DM version: 3
+    INFO:rsciio.digital_micrograph:size 4796607 B
+    INFO:rsciio.digital_micrograph:Is file Little endian? True
+    INFO:rsciio.digital_micrograph:Total tags in root group: 15
+
+    """
+    logger = initialize_logger("rsciio")
+    logger.setLevel(level)
+
+
+class ColoredFormatter(logging.Formatter):
+    """Colored log formatter.
+
+    This class is used to format the log output. The colors can be changed
+    by changing the ANSI escape codes in the class variables.
+
+    This is a modified version of both this
+    https://github.com/herzog0/best_python_logger
+    and this https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output/56944256#56944256
+    """
+
+    grey = "\x1b[38;20m"
+    yellow = "\x1b[33;20m"
+    red = "\x1b[31;20m"
+    bold_red = "\x1b[31;1m"
+    reset = "\x1b[0m"
+    green = "\x1b[1;32m"
+    format = "%(levelname)s | RosettaSciIO | %(message)s (%(name)s:%(lineno)d)"
+
+    FORMATS = {
+        logging.DEBUG: grey + format + reset,
+        logging.INFO: green + format + reset,
+        logging.WARNING: yellow + format + reset,
+        logging.ERROR: red + format + reset,
+        logging.CRITICAL: bold_red + format + reset,
+    }
+
+    def format(self, record):
+        log_fmt = self.FORMATS.get(record.levelno)
+        formatter = logging.Formatter(log_fmt)
+        return formatter.format(record)
+
+
+def initialize_logger(*args):
+    """Creates a pretty logging instance where the colors can be changed
+    via the ColoredFormatter class.  Any arguments passed to initialize_logger
+    will be passed to `logging.getLogger`
+
+    The logging output will also be redirected from the standard error file to
+    the standard output file.
+    """
+    formatter = ColoredFormatter()
+    handler = logging.StreamHandler(sys.stdout)
+    handler.setFormatter(formatter)
+
+    _logger = logging.getLogger(*args)
+    # Remove existing handler
+    while len(_logger.handlers):
+        _logger.removeHandler(_logger.handlers[0])
+    _logger.addHandler(handler)
+    return _logger
diff --git a/rsciio/_version.py b/rsciio/_version.py
deleted file mode 100644
index 8cfa14250..000000000
--- a/rsciio/_version.py
+++ /dev/null
@@ -1,1 +0,0 @@
-__version__ = "0.2.dev0"
diff --git a/rsciio/ripple/_api.py b/rsciio/ripple/_api.py
index 0f2767206..094d9d514 100644
--- a/rsciio/ripple/_api.py
+++ b/rsciio/ripple/_api.py
@@ -36,7 +36,7 @@
     RETURNS_DOC,
     SIGNAL_DOC,
 )
-from rsciio._version import __version__
+from rsciio import __version__
 from rsciio.utils.tools import DTBox
 
 _logger = logging.getLogger(__name__)
diff --git a/upcoming_changes/69.new.rst b/upcoming_changes/69.new.rst
new file mode 100644
index 000000000..e6dce4d60
--- /dev/null
+++ b/upcoming_changes/69.new.rst
@@ -0,0 +1,1 @@
+Add :func:`rsciio.set_log_level` to set the logging level of ``RosettaSciIO``
\ No newline at end of file


Test patch:
diff --git a/rsciio/tests/test_import.py b/rsciio/tests/test_import.py
index dbfb17f0c..94dd6b7be 100644
--- a/rsciio/tests/test_import.py
+++ b/rsciio/tests/test_import.py
@@ -28,7 +28,7 @@ def test_import_version():
 
 
 def test_rsciio_dir():
-    assert dir(rsciio) == ["IO_PLUGINS", "__version__"]
+    assert dir(rsciio) == ["IO_PLUGINS", "__version__", "set_log_level"]
 
 
 def test_rsciio_utils():
diff --git a/rsciio/tests/test_set_log_level.py b/rsciio/tests/test_set_log_level.py
new file mode 100644
index 000000000..964e91240
--- /dev/null
+++ b/rsciio/tests/test_set_log_level.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Copyright 2007-2023 The HyperSpy developers
+#
+# This file is part of RosettaSciIO.
+#
+# RosettaSciIO is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# RosettaSciIO is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with RosettaSciIO. If not, see <https://www.gnu.org/licenses/#GPL>.
+
+import pytest
+
+import rsciio
+
+
+@pytest.mark.parametrize(
+    "level", ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET")
+)
+def test_set_log_level(level):
+    rsciio.set_log_level(level)