diff --git a/python_boilerplate/configuration/loguru_configuration.py b/python_boilerplate/configuration/loguru_configuration.py index 3c5d3ce..9699b8e 100644 --- a/python_boilerplate/configuration/loguru_configuration.py +++ b/python_boilerplate/configuration/loguru_configuration.py @@ -3,6 +3,8 @@ import sys from logging import LogRecord +import arrow +from arrow import Arrow from loguru import logger from python_boilerplate.common.common_function import get_data_dir, get_module_name @@ -19,8 +21,9 @@ # Remove a previously added handler and stop sending logs to its sink. logger.remove(handler_id=None) # Set up logging for log file +_logs_directory_path = get_data_dir("logs") _log_file = ( - str(get_data_dir("logs")) + str(_logs_directory_path) + f"/{get_module_name()}.{platform.node()}." + "{time}.log" ) @@ -34,7 +37,7 @@ backtrace=True, diagnose=True, rotation="00:00", - retention="7 Days", + retention="7 days", compression="gz", serialize=False, ) @@ -78,8 +81,24 @@ def emit(self, record: LogRecord) -> None: logger.info(f"Configured logger[{key}]'s level to {value}") +def retain_log_files() -> None: + now = arrow.get() + dates = { + date.format("YYYY-MM-DD") + for date in Arrow.range("day", now.shift(days=-7), end=now) + } + for file_path in _logs_directory_path.glob("*.log"): + split_log_file_name = file_path.name.split(".") + split_log_file_name.reverse() + date_in_file_name = split_log_file_name[1][0:10] + if date_in_file_name not in dates: + logger.debug(f"Deleting log: {file_path}") + file_path.unlink(missing_ok=True) + + def configure() -> None: """ Configure logging. """ + retain_log_files() logger.warning(f"Loguru logging configured, log_level: {log_level}") diff --git a/python_boilerplate/demo/async_demo.py b/python_boilerplate/demo/async_usage.py similarity index 100% rename from python_boilerplate/demo/async_demo.py rename to python_boilerplate/demo/async_usage.py diff --git a/tests/common/test_profiling.py b/tests/common/test_profiling.py index 501e4e6..cbcf9d3 100644 --- a/tests/common/test_profiling.py +++ b/tests/common/test_profiling.py @@ -3,15 +3,23 @@ import pytest -from python_boilerplate.common.profiling import async_elapsed_time, elapsed_time +from python_boilerplate.common.profiling import ( + async_elapsed_time, + cpu_profile, + elapsed_time, + mem_profile, +) +@mem_profile() @elapsed_time(level="WARNING") def time_consuming_function() -> str: sleep(3) return "done execution" +@cpu_profile() +@mem_profile() @elapsed_time(level="INFO") def time_consuming_function_raising_error() -> None: sleep(1) diff --git a/tests/configuration/test_loguru_configuration.py b/tests/configuration/test_loguru_configuration.py index 71b2e83..6b38c79 100644 --- a/tests/configuration/test_loguru_configuration.py +++ b/tests/configuration/test_loguru_configuration.py @@ -1,6 +1,12 @@ +from pathlib import Path + from loguru import logger +from pytest_mock import MockerFixture -from python_boilerplate.configuration.loguru_configuration import configure +from python_boilerplate.configuration.loguru_configuration import ( + configure, + retain_log_files, +) def test_configure() -> None: @@ -16,3 +22,16 @@ def test_log_exception() -> None: logger.info(f"divided_by_zero = {divided_by_zero}") except Exception as ex: logger.exception(f"Oops! Exception occurred. {ex}") + + +def test_retain_log_files(mocker: MockerFixture) -> None: + path_list = [ + Path( + "python_boilerplate.johnnys-macbook-pro-2017.local.2023-05-01_08-42-03_086829.log" + ) + ] + patch = mocker.patch( + "pathlib.Path.glob", return_value=(element for element in path_list) + ) + retain_log_files() + patch.assert_called() diff --git a/tests/demo/test_async_demo.py b/tests/demo/test_async_usage.py similarity index 92% rename from tests/demo/test_async_demo.py rename to tests/demo/test_async_usage.py index 0304a62..ca9884b 100644 --- a/tests/demo/test_async_demo.py +++ b/tests/demo/test_async_usage.py @@ -4,10 +4,11 @@ import pytest from loguru import logger -from python_boilerplate.demo.async_demo import ( +from python_boilerplate.demo.async_usage import ( coroutine1, coroutine2, coroutine3, + main, non_coroutine, ) @@ -92,3 +93,11 @@ async def test_running_coroutine1_2_3_concurrently() -> None: assert type(gathered_results[0]) == int assert type(gathered_results[1]) == str assert type(gathered_results[2]) is ValueError + + +@pytest.mark.asyncio +async def test_main() -> None: + try: + await main() + except Exception as e: + assert False, f"Unexpected exception: {e}"