diff --git a/CHANGELOG.md b/CHANGELOG.md index b330f8f..2fcb1e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ to and from dicts. (#87) + `peewee-moves` was updated to v2.0.1. + Documentation is now reStructuredText and is hosted by ReadTheDocs (#91) ++ Switched to using `loguru` for logging. (#94) ## v0.3.0 (2019-01-28) diff --git a/requirements.txt b/requirements.txt index 6ffe53f..aae8594 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ peewee==3.8.0 requests==2.21.0 celery[redis]==4.2.1 peewee-moves==2.0.1 +loguru==0.2.5 diff --git a/src/trendlines/__init__.py b/src/trendlines/__init__.py index 0f282bf..c913555 100644 --- a/src/trendlines/__init__.py +++ b/src/trendlines/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from trendlines._logging import setup_logging -logger = setup_logging(to_console=True, to_file=False) +from loguru import logger from trendlines.app_factory import create_app diff --git a/src/trendlines/_logging.py b/src/trendlines/_logging.py index 59a0743..f11ae80 100644 --- a/src/trendlines/_logging.py +++ b/src/trendlines/_logging.py @@ -2,131 +2,56 @@ """ """ import logging -import time -import zlib -from pathlib import Path -from logging.handlers import RotatingFileHandler +import sys -LOG_FMT = ( - "%(asctime)s.%(msecs)03dZ" - "|%(levelname)-8.8s" - "|%(module)-18.18s" - "|%(lineno)4d" - "|%(funcName)-16.16s" - "|%(message)s" -) -DATE_FMT = "%Y-%m-%dT%H-%M-%S" - - -class CustomFormatter(logging.Formatter): - # Record times in UTC. Because that's the smart thing to do. - converter = time.gmtime - - def format(self, record): - return super(CustomFormatter, self).format(record) - - -def setup_logging(to_console=True, to_file=False, log_path=None): +def setup_logging(logger, to_console=True, to_file=False, log_path=None): """ Setup logging for this project. Parameters ---------- + logger : :class:`loguru.Logger` + The Loguru logger object. to_console : bool, optional If ``True``, enable logging to the console. to_file : bool, optional If ``True``, enable logging to a file. log_path : str, optional The file to log to, if ``to_file`` is ``True``. - - Returns - ------- - logger : :class:`logging.Logger` - The created logger instance. """ - logger = logging.getLogger("trendlines") - logger.setLevel(logging.DEBUG) - if to_console: _setup_console_logging(logger) if to_file: - _setup_file_logging(logger, Path(log_path)) - - return logger - - -# Custom namer and rotator -# Taken from https://docs.python.org/3/howto/logging-cookbook.html -def _gzip_namer(name): - return name + ".gz" - - -def _gzip_rotator(source, dest): - """ - Compress the source file into the dest. - - Keeps the most recent rotation uncompressed. - - Parameters - ---------- - source : :class:`pathlib.Path` - dest : :class:`pathlib.Path` - """ - with open(str(source), 'rb') as open_source: - data = open_source.read() - compressed = zlib.compress(data) - with open(str(dest), 'wb') as open_dest: - open_dest.write(compressed) - - # Keep a copy of the most recent rotation uncompressed to make things - # easier for users. - source.replace(dest + ".1") - - source.unlink() + _setup_file_logging(logger, log_path) def _setup_file_logging(logger, log_path): """ Parameters ---------- - logger : + logger : :class:`loguru.logger` The logger to modify log_path : :class:`pathlib.Path` The file and path to log to. """ - name = "File Handler" - - # Make our log dir and file. - log_path.mkdir(mode=0x0755, parents=True, exist_ok=True) - log_path.chmod(0x0664) + logger.add(log_path, + rotation="1 week", + retention="20 weeks", + compression="tar.gz", + format='{time:YYYY-MM-DD HH:mm:ss.SSSZZ} | {level: <8} | {name}:{function}:{line} - {message}', + ) - # Create the handler and have it compress old files. - handler = RotatingFileHandler(str(log_path), maxBytes=1e7) # ~10MB - handler.rotator = _gzip_rotator - handler.namer = _gzip_namer - - handler.setLevel(logging.DEBUG) - handler.setFormatter(CustomFormatter(LOG_FMT, DATE_FMT)) - handler.set_name(name) - if name not in [h.name for h in logger.handlers]: - logger.addHandler(handler) - logger.info("File logging initialized.") + logger.info("File logging initialized.") def _setup_console_logging(logger): """ Parameters ---------- - logger : :class:`logging.Logger` + logger : :class:`loguru.logger` + The logger instance to modify. """ - name = "Console Handler" - - # Create the handler. Console handlers are easy. - handler = logging.StreamHandler() - handler.setLevel(logging.DEBUG) - handler.setFormatter(CustomFormatter(LOG_FMT, DATE_FMT)) - handler.set_name(name) - if name not in [h.name for h in logger.handlers]: - logger.addHandler(handler) - logger.info("Console logging initialized.") + # This is only here in case I decide I want to make changes. For now + # it's a noop. + logger.info("Console logging initialized.") diff --git a/src/trendlines/app_factory.py b/src/trendlines/app_factory.py index 9cb0a48..d2fb66e 100644 --- a/src/trendlines/app_factory.py +++ b/src/trendlines/app_factory.py @@ -18,7 +18,7 @@ def create_app(): """ Primary application factory. """ - _logging.setup_logging() + _logging.setup_logging(logger) logger.debug("Creating app.") app = Flask(__name__) diff --git a/tests/conftest.py b/tests/conftest.py index 068a504..f77a97e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,17 +2,30 @@ """ Global PyTest fixtures. """ +import logging from pathlib import Path import pytest +from _pytest.logging import caplog as _caplog from trendlines import db +from trendlines import logger from trendlines.app_factory import create_app from trendlines.app_factory import create_db from trendlines.orm import DataPoint from trendlines.orm import Metric +@pytest.fixture +def caplog(_caplog): + class PropogateHandler(logging.Handler): + def emit(self, record): + logging.getLogger(record.name).handle(record) + handler_id = logger.add(PropogateHandler(), format="{message}") + yield _caplog + logger.remove(handler_id) + + @pytest.fixture def app(): """