diff options
Diffstat (limited to 'fg21sim')
| -rw-r--r-- | fg21sim/configs/00-general.conf.spec | 34 | ||||
| -rw-r--r-- | fg21sim/configs/manager.py | 44 | ||||
| -rw-r--r-- | fg21sim/utils/__init__.py | 1 | ||||
| -rw-r--r-- | fg21sim/utils/healpix.py | 30 | ||||
| -rw-r--r-- | fg21sim/utils/logging.py | 85 | 
5 files changed, 182 insertions, 12 deletions
diff --git a/fg21sim/configs/00-general.conf.spec b/fg21sim/configs/00-general.conf.spec new file mode 100644 index 0000000..c1a0eb3 --- /dev/null +++ b/fg21sim/configs/00-general.conf.spec @@ -0,0 +1,34 @@ +# Configurations for "fg21sim" +# -*- mode: conf -*- +# +# Syntax: `ConfigObj`, https://github.com/DiffSK/configobj +# +# This file contains the general configurations, which control the general +# behaviors, or will be used in other configuration sections. + +[logging] +# DEBUG:    Detailed information, typically of interest only when diagnosing +#           problems. +# INFO:     Confirmation that things are working as expected. +# WARNING:  An dinciation that something unexpected happended, or indicative +#           of some problem in the near future (e.g., "disk space low"). +#           The software is still working as expected. +# ERROR:    Due to a more serious problem, the software has not been able to +#           perform some function. +# CRITICAL: A serious error, indicating that the program itself may be unable +#           to continue running. +level = option("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", default="INFO") + +# Set the format of displayed messages +format = string(default="%(asctime)s [%(levelname)s] %(name)s: %(message)s") + +# Set the date/time format in messages (default: ISO8601) +datefmt = string(default="%Y-%m-%dT%H:%M:%S") + +# Set the logging filename (will create a `FileHandler`) +filename = string(default="") +# Set the mode to open the above logging file +filemode = option("w", "a", default="a") + +# Set the stream used to initialize the `StreamHandler` +stream = option("stderr", "stdout", "", default="stderr") diff --git a/fg21sim/configs/manager.py b/fg21sim/configs/manager.py index 933a63d..f5465a9 100644 --- a/fg21sim/configs/manager.py +++ b/fg21sim/configs/manager.py @@ -10,13 +10,19 @@ Configuration manager.  """  import os +import sys  from glob import glob -from errors import ConfigError +import logging  from configobj import ConfigObj, ConfigObjError, flatten_errors  from validate import Validator +class ConfigError(Exception): +    """Could not parse user configurations""" +    pass + +  CONFIGS_PATH = os.path.dirname(__file__) @@ -30,21 +36,23 @@ class ConfigManager:          spec = "\n".join([open(f).read() for f in configs_spec]).split("\n")          self._configspec = ConfigObj(spec, interpolation=False,                                       list_values=False, _inspec=True) -        self._validator = Validator() -        configs_default = ConfigObj(configspec=self._configspec) +        configs_default = ConfigObj(interpolation=False, +                                    configspec=self._configspec)          self._config = self.validate(configs_default)          if configs:              for config in configs:                  self.read_config(config)      def read_config(self, config): -        newconfig = ConfigObj(config, configspec=self._configspec) +        newconfig = ConfigObj(config, interpolation=False, +                              configspec=self._configspec)          newconfig = self.validate(newconfig)          self._config.merge(newconfig)      def validate(self, config): +        validator = Validator()          try: -            results = config.validate(self._validator, preserve_errors=True) +            results = config.validate(validator, preserve_errors=True)          except ConfigObjError as e:              raise ConfigError(e.message)          if not results: @@ -72,3 +80,29 @@ class ConfigManager:      def set(self, key, value):          self._config[key] = value + +    @property +    def logging(self): +        """Get and prepare the logging configurations for +        `logging.basicConfig()` +        """ +        from logging import FileHandler, StreamHandler +        conf = self.get("logging") +        # logging handlers +        handlers = [] +        stream = conf["stream"] +        if stream: +            handlers.append(StreamHandler(getattr(sys, stream))) +        logfile = conf["filename"] +        filemode = conf["filemode"] +        if logfile: +            handlers.append(FileHandler(logfile, mode=filemode)) +        # +        logconf = { +            "level": getattr(logging, conf["level"]), +            "format": conf["format"], +            "datefmt": conf["datefmt"], +            "filemode": filemode, +            "handlers": handlers, +        } +        return logconf diff --git a/fg21sim/utils/__init__.py b/fg21sim/utils/__init__.py index 3548b3f..650ab37 100644 --- a/fg21sim/utils/__init__.py +++ b/fg21sim/utils/__init__.py @@ -2,3 +2,4 @@  # MIT license  from .healpix import healpix2hpx, hpx2healpix +from .logging import setup_logging diff --git a/fg21sim/utils/healpix.py b/fg21sim/utils/healpix.py index af0df59..acd85ef 100644 --- a/fg21sim/utils/healpix.py +++ b/fg21sim/utils/healpix.py @@ -33,7 +33,7 @@ from astropy.io import fits  from .. import logging -logger = logging.getLogger() +logger = logging.getLogger(__name__)  def healpix2hpx(data, header=None, append_history=None, append_comment=None): @@ -61,11 +61,16 @@ def healpix2hpx(data, header=None, append_history=None, append_comment=None):          hp_data, hp_header = hp.read_map(data, nest=False, h=True,                                           verbose=False)          hp_header = fits.header.Header(hp_header) +        logger.info("Read HEALPix data from file or HDU")      else:          hp_data, hp_header = np.asarray(data), fits.header.Header(header) +        logger.info("Read HEALPix data from array and header") +    logger.info("HEALPix index ordering: %s" % hp_header["ORDERING"])      if hp_header["ORDERING"] != "RING":          raise ValueError("only 'RING' ordering currently supported") -    nside = hp.npix2nside(len(hp_data)) +    npix = len(hp_data) +    nside = hp.npix2nside(npix) +    logger.info("HEALPix data: Npix=%d, Nside=%d" % (npix, nside))      if nside != hp_header["NSIDE"]:          raise ValueError("HEALPix data Nside does not match the header")      hp_data = np.concatenate([hp_data, [np.nan]]) @@ -103,20 +108,24 @@ def hpx2healpix(data, header=None, append_history=None, append_comment=None):      if isinstance(data, str):          hpx_hdu = fits.open(data)[0]          hpx_data, hpx_header = hpx_hdu.data, hpx_hdu.header +        logger.info("Read HPX image from file")      elif isinstance(data, fits.PrimaryHDU):          hpx_data, hpx_header = data.data, data.header +        logger.info("Read HPX image from HDU")      else:          hpx_data, hpx_header = np.asarray(data), fits.header.Header(header) +        logger.info("Read HPX image from array and header") +    logger.info("HPX coordinate system: ({0}, {1})".format( +        hpx_header["CTYPE1"], hpx_header["CTYPE2"]))      if ((hpx_header["CTYPE1"], hpx_header["CTYPE2"]) !=              ("GLON-HPX", "GLAT-HPX")): -        logger.debug("Input coordinate system: ({0}, {1})".format( -            hpx_header["CTYPE1"], hpx_header["CTYPE2"]))          raise ValueError("only Galactic 'HPX' projection currently supported")      # calculate Nside      nside = round(hpx_header["NAXIS1"] / 5)      nside2 = round(90 / np.sqrt(2) / hpx_header["CDELT2"])      if nside != nside2:          raise ValueError("Cannot determine the Nside value") +    logger.info("Determined HEALPix Nside=%d" % nside)      #      npix = hp.nside2npix(nside)      hpx_idx = _calc_hpx_indices(nside).flatten() @@ -150,7 +159,7 @@ def _calc_hpx_indices(nside):      ----      * The indices are 0-based;      * Currently only HEALPix RING ordering supported; -    * The null/empty elements in the HPX projection are filled with '-1'. +    * The null/empty elements in the HPX projection are filled with "-1".      """      # number of horizontal/vertical facet      nfacet = 5 @@ -166,6 +175,7 @@ def _calc_hpx_indices(nside):      #      shape = (nfacet*nside, nfacet*nside)      indices = -np.ones(shape).astype(np.int) +    logger.info("HPX indices matrix shape: {0}".format(shape))      #      # Loop vertically facet-by-facet      for jfacet in range(nfacet): @@ -285,6 +295,7 @@ def _make_hpx_header(header, append_history=None, append_comment=None):                          "[deg] Galactic latitude at the reference point")      header["PV2_1"] = (4, "HPX H parameter (longitude)")      header["PV2_2"] = (3, "HPX K parameter (latitude)") +    logger.info("Made HPX FITS header")      #      header["DATE"] = (          datetime.now(timezone.utc).astimezone().isoformat(), @@ -301,9 +312,11 @@ def _make_hpx_header(header, append_history=None, append_comment=None):          header.add_comment(comment)      #      if append_history is not None: +        logger.info("HPX FITS header: append history")          for history in append_history:              header.add_history(history) -    if append_history is not None: +    if append_comment is not None: +        logger.info("HPX FITS header: append comments")          for comment in append_comment:              header.add_comment(comment)      return header @@ -323,6 +336,7 @@ def _make_healpix_header(header, nside,      header["NPIX"] = (npix, "Total number of pixels")      header["FIRSTPIX"] = (0, "First pixel # (0 based)")      header["LASTPIX"] = (npix-1, "Last pixel # (0 based)") +    logger.info("Made HEALPix FITS header")      #      header["DATE"] = (          datetime.now(timezone.utc).astimezone().isoformat(), @@ -330,9 +344,11 @@ def _make_healpix_header(header, nside,      )      #      if append_history is not None: +        logger.info("HEALPix FITS header: append history")          for history in append_history:              header.add_history(history) -    if append_history is not None: +    if append_comment is not None: +        logger.info("HEALPix FITS header: append comments")          for comment in append_comment:              header.add_comment(comment)      return header diff --git a/fg21sim/utils/logging.py b/fg21sim/utils/logging.py new file mode 100644 index 0000000..0abf4d3 --- /dev/null +++ b/fg21sim/utils/logging.py @@ -0,0 +1,85 @@ +# Copyright (c) 2016 Weitian LI <liweitianux@live.com> +# MIT license + +""" +Logging utilities. +""" + +import sys +import logging +from logging import FileHandler, StreamHandler + + +def setup_logging(dict_config=None, level=None, stream=None, logfile=None): +    """Setup the logging. +    This will override the logging configurations in the config file +    if specified (e.g., by command line arguments). + +    Parameters +    ---------- +    dict_config : dict +        Dict of logging configurations specified in the config file. +    level : string; +        Override the existing log level +    stream : string; "stderr", "stdout", or "" +        This controls where the log messages go to. +        If not None, then override the old ``StreamHandler`` settings; +        if ``stream=""``, then disable the ``StreamHandler``. +    logfile : string +        Specify the file where the log messages go to. +        If ``logfile=""``, then disable the ``FileHandler``. + +    NOTE +    ---- +    If the logging already has ``StreamHandler`` or ``FileHandler`` +    configured, then the old handler will be *replaced* (i.e., remove +    the old one, then add the new one). +    """ +    # default file open mode for logging to file +    filemode = "a" +    # +    if dict_config: +        # XXX: +        # "basicConfig()" does NOT accept paramter "filemode" if the +        # corresponding parameter "filename" NOT specified. +        filemode = dict_config.pop("filemode", filemode) +        logging.basicConfig(**dict_config) +    # +    root_logger = logging.getLogger() +    # +    if level is not None: +        level_int = getattr(logging, level.upper(), None) +        if not isinstance(level_int, int): +            raise ValueError("invalid log level: %s" % level) +        root_logger.setLevel(level_int) +    # +    if stream is None: +        pass +    elif stream in ["", "stderr", "stdout"]: +        for handler in root_logger.handlers: +            if isinstance(handler, StreamHandler): +                # remove old ``StreamHandler`` +                root_logger.removeHandler(handler) +        if stream == "": +            # disable ``StreamHandler`` +            pass +        else: +            # add new ``StreamHandler`` +            handler = StreamHandler(getattr(sys, stream)) +            root_logger.addHandler(handler) +    else: +        raise ValueError("invalid stream: %s" % stream) +    # +    if logfile is not None: +        for handler in root_logger.handlers: +            if isinstance(handler, FileHandler): +                filemode = handler.mode +                # remove old ``FileHandler`` +                root_logger.removeHandler(handler) +        if logfile == "": +            # disable ``FileHandler`` +            pass +        else: +            # add new ``FileHandler`` +            handler = FileHandler(logfile, mode=filemode) +            root_logger.addHandler(handler)  | 
