diff --git a/.gitignore b/.gitignore
index 7cdd6cde..f34de73e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -138,7 +138,6 @@ dmypy.json
# Pyre type checker
.pyre/
-
# VScode
.vscode/
app/.vscode/
diff --git a/app/config.py.example b/app/config.py.example
index b165b5f1..50b524b0 100644
--- a/app/config.py.example
+++ b/app/config.py.example
@@ -13,7 +13,7 @@ class Settings(BaseSettings):
env_file = ".env"
-# general
+# GENERAL
DOMAIN = 'Our-Domain'
# DATABASE
@@ -29,11 +29,11 @@ AVATAR_SIZE = (120, 120)
# API-KEYS
WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')
-# export
+# EXPORT
ICAL_VERSION = '2.0'
PRODUCT_ID = '-//Our product id//'
-# email
+# EMAIL
email_conf = ConnectionConfig(
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",
@@ -47,3 +47,14 @@ email_conf = ConnectionConfig(
# PATHS
STATIC_ABS_PATH = os.path.abspath("static")
+
+# LOGGER
+LOG_PATH = "./var/log"
+LOG_FILENAME = "calendar.log"
+LOG_LEVEL = "error"
+LOG_ROTATION_INTERVAL = "20 days"
+LOG_RETENTION_INTERVAL = "1 month"
+LOG_FORMAT = ("{level: <8}"
+ " {time:YYYY-MM-DD HH:mm:ss.SSS}"
+ " - {name}:{function}"
+ " - {message}")
diff --git a/app/internal/logger_customizer.py b/app/internal/logger_customizer.py
new file mode 100644
index 00000000..274f6c74
--- /dev/null
+++ b/app/internal/logger_customizer.py
@@ -0,0 +1,94 @@
+import sys
+
+from pathlib import Path
+from loguru import logger, _Logger as Logger
+
+
+class LoggerConfigError(Exception):
+ pass
+
+
+class LoggerCustomizer:
+
+ @classmethod
+ def make_logger(cls, log_path: Path,
+ log_filename: str,
+ log_level: str,
+ log_rotation_interval: str,
+ log_retention_interval: str,
+ log_format: str) -> Logger:
+ """Creates a logger from given configurations
+
+ Args:
+ log_path (Path): Path where the log file is located
+ log_filename (str):
+
+ log_level (str): The level we want to start logging from
+ log_rotation_interval (str): Every how long the logs
+ would be rotated
+ log_retention_interval (str): Amount of time in words defining
+ how long the log will be kept
+ log_format (str): The logging format
+
+ Raises:
+ LoggerConfigError: Error raised when the configuration is invalid
+
+ Returns:
+ Logger: Loguru logger instance
+ """
+ try:
+ logger = cls.customize_logging(
+ file_path=Path(log_path) / Path(log_filename),
+ level=log_level,
+ retention=log_retention_interval,
+ rotation=log_rotation_interval,
+ format=log_format
+ )
+ except (TypeError, ValueError) as err:
+ raise LoggerConfigError(
+ f"You have an issue with the logger configuration: {err!r}, "
+ "fix it please")
+
+ return logger
+
+ @classmethod
+ def customize_logging(cls,
+ file_path: Path,
+ level: str,
+ rotation: str,
+ retention: str,
+ format: str
+ ) -> Logger:
+ """Used to customize the logger instance
+
+ Args:
+ file_path (Path): Path where the log file is located
+ level (str): The level wanted to start logging from
+ rotation (str): Every how long the logs would be
+ rotated(creation of new file)
+ retention (str): Amount of time in words defining how
+ long a log is kept
+ format (str): The logging format
+
+ Returns:
+ Logger: Instance of a logger mechanism
+ """
+ logger.remove()
+ logger.add(
+ sys.stdout,
+ enqueue=True,
+ backtrace=True,
+ level=level.upper(),
+ format=format
+ )
+ logger.add(
+ str(file_path),
+ rotation=rotation,
+ retention=retention,
+ enqueue=True,
+ backtrace=True,
+ level=level.upper(),
+ format=format
+ )
+
+ return logger
diff --git a/app/main.py b/app/main.py
index 9f562d34..c72c418e 100644
--- a/app/main.py
+++ b/app/main.py
@@ -9,6 +9,8 @@
from app.routers import (
agenda, dayview, email, event, invitation, profile, search, telegram)
from app.telegram.bot import telegram_bot
+from app.internal.logger_customizer import LoggerCustomizer
+from app import config
def create_tables(engine, psql_environment):
@@ -27,6 +29,16 @@ def create_tables(engine, psql_environment):
app.mount("/static", StaticFiles(directory=STATIC_PATH), name="static")
app.mount("/media", StaticFiles(directory=MEDIA_PATH), name="media")
+# Configure logger
+logger = LoggerCustomizer.make_logger(config.LOG_PATH,
+ config.LOG_FILENAME,
+ config.LOG_LEVEL,
+ config.LOG_ROTATION_INTERVAL,
+ config.LOG_RETENTION_INTERVAL,
+ config.LOG_FORMAT)
+app.logger = logger
+
+
app.include_router(profile.router)
app.include_router(event.router)
app.include_router(agenda.router)
@@ -40,6 +52,7 @@ def create_tables(engine, psql_environment):
@app.get("/")
+@app.logger.catch()
async def home(request: Request):
return templates.TemplateResponse("home.html", {
"request": request,
diff --git a/requirements.txt b/requirements.txt
index d1cf56d5..0159b2af 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -39,6 +39,7 @@ iniconfig==1.1.1
Jinja2==2.11.2
joblib==1.0.0
lazy-object-proxy==1.5.2
+loguru==0.5.3
mypy==0.790
mypy-extensions==0.4.3
MarkupSafe==1.1.1
@@ -86,4 +87,4 @@ watchgod==0.6
websockets==8.1
word-forms==2.1.0
wsproto==1.0.0
-zipp==3.4.0
+zipp==3.4.0
\ No newline at end of file
diff --git a/tests/conftest.py b/tests/conftest.py
index 1100fc70..33ff6148 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -12,6 +12,7 @@
'tests.association_fixture',
'tests.client_fixture',
'tests.asyncio_fixture',
+ 'tests.logger_fixture',
'smtpdfix',
]
diff --git a/tests/logger_fixture.py b/tests/logger_fixture.py
new file mode 100644
index 00000000..2684d336
--- /dev/null
+++ b/tests/logger_fixture.py
@@ -0,0 +1,31 @@
+import logging
+
+import pytest
+from _pytest.logging import caplog as _caplog # noqa: F401
+from loguru import logger
+
+from app import config
+from app.internal.logger_customizer import LoggerCustomizer
+
+
+@pytest.fixture(scope='module')
+def logger_instance():
+ _logger = LoggerCustomizer.make_logger(config.LOG_PATH,
+ config.LOG_FILENAME,
+ config.LOG_LEVEL,
+ config.LOG_ROTATION_INTERVAL,
+ config.LOG_RETENTION_INTERVAL,
+ config.LOG_FORMAT)
+
+ return _logger
+
+
+@pytest.fixture
+def caplog(_caplog): # noqa: F811
+ class PropagateHandler(logging.Handler):
+ def emit(self, record):
+ logging.getLogger(record.name).handle(record)
+
+ handler_id = logger.add(PropagateHandler(), format="{message} {extra}")
+ yield _caplog
+ logger.remove(handler_id)
diff --git a/tests/test_logger.py b/tests/test_logger.py
new file mode 100644
index 00000000..0c4cd97e
--- /dev/null
+++ b/tests/test_logger.py
@@ -0,0 +1,42 @@
+import logging
+
+import pytest
+
+from app.internal.logger_customizer import LoggerCustomizer, LoggerConfigError
+from app import config
+
+
+class TestLogger:
+ @staticmethod
+ def test_log_debug(caplog, logger_instance):
+ with caplog.at_level(logging.DEBUG):
+ logger_instance.debug('Is it debugging now?')
+ assert 'Is it debugging now?' in caplog.text
+
+ @staticmethod
+ def test_log_info(caplog, logger_instance):
+ with caplog.at_level(logging.INFO):
+ logger_instance.info('App started')
+ assert 'App started' in caplog.text
+
+ @staticmethod
+ def test_log_error(caplog, logger_instance):
+ with caplog.at_level(logging.ERROR):
+ logger_instance.error('Something bad happened!')
+ assert 'Something bad happened!' in caplog.text
+
+ @staticmethod
+ def test_log_critical(caplog, logger_instance):
+ with caplog.at_level(logging.CRITICAL):
+ logger_instance.critical("WE'RE DOOMED!")
+ assert "WE'RE DOOMED!" in caplog.text
+
+ @staticmethod
+ def test_bad_configuration():
+ with pytest.raises(LoggerConfigError):
+ LoggerCustomizer.make_logger(config.LOG_PATH,
+ config.LOG_FILENAME,
+ 'eror',
+ config.LOG_ROTATION_INTERVAL,
+ config.LOG_RETENTION_INTERVAL,
+ config.LOG_FORMAT)