Skip to content

Commit

Permalink
Implement a custom DKISTLogger
Browse files Browse the repository at this point in the history
  • Loading branch information
Cadair committed Jan 23, 2024
1 parent 9e21599 commit c286dc8
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 14 deletions.
20 changes: 12 additions & 8 deletions dkist/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
"""
The DKIST package aims to help you search, obtain and use DKIST data as part of your Python software.
"""
from pkg_resources import DistributionNotFound, get_distribution
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as _version

import astropy.config as _config

from .dataset import Dataset, TiledDataset, load_dataset # noqa
from .utils.sysinfo import system_info # noqa
from .logger import log

try:
__version__ = get_distribution(__name__).version
except DistributionNotFound:
# package is not installed
__version__ = _version(__name__)
except PackageNotFoundError:
__version__ = "unknown"


__all__ = ['TiledDataset', 'Dataset', 'load_dataset', 'system_info']


Expand All @@ -25,4 +23,10 @@ def write_default_config(overwrite=False):
config file already exits this will write a config file appended with the
version number, to facilitate comparison of changes.
"""
import astropy.config as _config
return _config.create_config_file("dkist", "dkist", overwrite=overwrite)


# Do internal imports last (so logger etc is initialised)
from dkist.dataset import Dataset, TiledDataset, load_dataset # noqa
from dkist.utils.sysinfo import system_info # noqa
7 changes: 3 additions & 4 deletions dkist/io/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
"""

import abc
import logging
from pathlib import Path

import numpy as np

from astropy.io import fits
from sunpy.util.decorators import add_common_docstring

_LOGGER = logging.getLogger(__name__)
from dkist import log

__all__ = ['BaseFITSLoader', 'AstropyFITSLoader']

Expand Down Expand Up @@ -91,7 +90,7 @@ class AstropyFITSLoader(BaseFITSLoader):

def __getitem__(self, slc):
if not self.absolute_uri.exists():
_LOGGER.debug("File %s does not exist.", self.absolute_uri)
log.debug("File %s does not exist.", self.absolute_uri)
# Use np.broadcast_to to generate an array of the correct size, but
# which only uses memory for one value.
return np.broadcast_to(np.nan, self.shape) * np.nan
Expand All @@ -100,7 +99,7 @@ def __getitem__(self, slc):
memmap=False, # memmap is redundant with dask and delayed loading
do_not_scale_image_data=True, # don't scale as we shouldn't need to
mode="denywrite") as hdul:
_LOGGER.debug("Accessing slice %s from file %s", slc, self.absolute_uri)
log.debug("Accessing slice %s from file %s", slc, self.absolute_uri)

hdu = hdul[self.target]
if hasattr(hdu, "section"):
Expand Down
106 changes: 106 additions & 0 deletions dkist/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
This module contains helpers to use the Python logger to show messages to users.
It is heavily insipired by Astropy's logger, but implemented independantly
because Astropy warn you against directly using theirs.
This module sets up the following things:
* A `logging.Logger` subclass which:
- Tracks the module which triggered the log call.
- Overrides warnings.showwarnings so that subclasses of given warning classes are displayed using the logger.
* Sets up a ``log`` instance which uses the Astropy StreamHandler class to log to stdout and colourise the output.
"""
import os
import sys
import logging
import warnings

from astropy.logger import StreamHandler as AstropyStreamHandler
from astropy.utils.introspection import find_current_module

from dkist.utils.exceptions import DKISTWarning


class DKISTLogger(logging.Logger):
"""
A knock off AstropyLogger.
"""
_showwarning_orig = None

def __init__(self, name, level=logging.NOTSET, *, capture_warning_classes=None):
super().__init__(name, level=level)
self.capture_warning_classes = tuple(capture_warning_classes) if capture_warning_classes is not None else tuple()
if self._showwarning_orig is None:
self._showwarning_orig = warnings.showwarning
warnings.showwarning = self._showwarning

def makeRecord(
self,
name,
level,
pathname,
lineno,
msg,
args,
exc_info,
func=None,
extra=None,
sinfo=None,
):
if extra is None:
extra = {}

Check warning on line 52 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L51-L52

Added lines #L51 - L52 were not covered by tests

if "origin" not in extra:
current_module = find_current_module(1, finddiff=[True, "logging"])
if current_module is not None:
extra["origin"] = current_module.__name__

Check warning on line 57 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L54-L57

Added lines #L54 - L57 were not covered by tests
else:
extra["origin"] = "unknown"

Check warning on line 59 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L59

Added line #L59 was not covered by tests

return super().makeRecord(

Check warning on line 61 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L61

Added line #L61 was not covered by tests
name,
level,
pathname,
lineno,
msg,
args,
exc_info,
func=func,
extra=extra,
sinfo=sinfo,
)

def _showwarning(self, *args, **kwargs):
# Bail out if we are not catching a warning from Astropy
if not isinstance(args[0], self.capture_warning_classes):
return self._showwarning_orig(*args, **kwargs)

Check warning on line 77 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L76-L77

Added lines #L76 - L77 were not covered by tests

warning = args[0]
message = f"{warning.__class__.__name__}: {args[0]}"

Check warning on line 80 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L79-L80

Added lines #L79 - L80 were not covered by tests

mod_path = args[2]

Check warning on line 82 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L82

Added line #L82 was not covered by tests
# Now that we have the module's path, we look through sys.modules to
# find the module object and thus the fully-package-specified module
# name. The module.__file__ is the original source file name.
mod_name = None
mod_path, ext = os.path.splitext(mod_path)
for name, mod in list(sys.modules.items()):
try:

Check warning on line 89 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L86-L89

Added lines #L86 - L89 were not covered by tests
# Believe it or not this can fail in some cases:
# https://github.com/astropy/astropy/issues/2671
path = os.path.splitext(getattr(mod, "__file__", ""))[0]
except Exception:
continue
if path == mod_path:
mod_name = mod.__name__
break

Check warning on line 97 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L92-L97

Added lines #L92 - L97 were not covered by tests

if mod_name is not None:
self.warning(message, extra={"origin": mod_name})

Check warning on line 100 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L99-L100

Added lines #L99 - L100 were not covered by tests
else:
self.warning(message)

Check warning on line 102 in dkist/logger.py

View check run for this annotation

Codecov / codecov/patch

dkist/logger.py#L102

Added line #L102 was not covered by tests


log = DKISTLogger(__name__, level=logging.INFO, capture_warning_classes=[DKISTWarning])
log.addHandler(AstropyStreamHandler())
18 changes: 16 additions & 2 deletions dkist/utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
class DKISTDeprecationWarning(DeprecationWarning):
pass
class DKISTWarning(Warning):
"""
The base warning class from which all dkist warnings should inherit.
"""

class DKISTUserWarning(UserWarning, DKISTWarning):
"""
The primary warning class for dkist.
Use this if you do not need a specific type of warning.
"""

class DKISTDeprecationWarning(DeprecationWarning, DKISTWarning):
"""
A warning class to use when functionality will be changed or removed in a future version.
"""

0 comments on commit c286dc8

Please sign in to comment.