diff --git a/.gitignore b/.gitignore index 8927364..702c0ad 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ wheels/ # Virtual environments .venv -.vscode \ No newline at end of file +.vscode +*.log \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6479dba..81b1410 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,4 +8,5 @@ dependencies = [ "pydantic>=2.11.4", "typer==0.9.0", "click==8.1.3", + "loguru>=0.7.3", ] diff --git a/src/app.py b/src/app.py index f39c5b0..51d6728 100644 --- a/src/app.py +++ b/src/app.py @@ -1,6 +1,7 @@ import typer import os from helpers import get_targets, run_backup, ask_repokey +from logger import logger app = typer.Typer() @@ -13,10 +14,11 @@ def backup(): for target in targets: if not target.enabled: - print(f"Skipping disabled target: {target.path}") + logger.info(f"Skipping disabled target: {target.path}") continue result_info = run_backup(target) - print(result_info.model_dump_json(indent=2)) + logger.info(f"Backup completed for {target.path}") + logger.debug(result_info.model_dump_json(indent=2)) if __name__ == "__main__": diff --git a/src/helpers.py b/src/helpers.py index 5ff7e3d..572f853 100644 --- a/src/helpers.py +++ b/src/helpers.py @@ -6,6 +6,7 @@ from subprocess import run import os import typer from typing import Optional +from logger import logger # Constants JSON_TARGETS = Path(__file__).parent / "targets.json" @@ -73,6 +74,7 @@ def get_targets() -> list[BackupItem]: targets = json.load(f) return [BackupItem(**target) for target in targets] except (json.JSONDecodeError, FileNotFoundError) as e: + logger.error(f"Error loading targets: {e}") typer.echo(f"Error loading targets: {e}") return [] @@ -98,6 +100,7 @@ def add_target(target: BackupItem) -> bool: write_targets(targets) return True except Exception as e: + logger.error(f"Error adding target: {e}") typer.echo(f"Error adding target: {e}") return False @@ -122,6 +125,7 @@ def write_targets(targets: list[BackupItem]) -> bool: ) return True except Exception as e: + logger.error(f"Error writing targets: {e}") typer.echo(f"Error writing targets: {e}") return False @@ -162,7 +166,7 @@ def prune_backups(targets: list[BackupItem], daily: int = 1, weekly: int = 2, "--keep-yearly", str(yearly), target.target, ] - print("Pruning target:", target.path) + logger.info(f"Pruning target: {target.path}") prune_result = run(cmd, capture_output=True, text=True) if prune_result.returncode != 0: @@ -170,8 +174,8 @@ def prune_backups(targets: list[BackupItem], daily: int = 1, weekly: int = 2, results.append(result) continue - print("Pruned target:", target.path) - print("Compacting cache") + logger.info(f"Pruned target: {target.path}") + logger.info("Compacting cache") compact_result = run(["borg", "compact", target.target], capture_output=True, text=True) if compact_result.returncode != 0: @@ -179,7 +183,7 @@ def prune_backups(targets: list[BackupItem], daily: int = 1, weekly: int = 2, results.append(result) continue - print("Compacted cache") + logger.info("Compacted cache") result["success"] = True result["message"] = "Pruned and compacted successfully" @@ -226,11 +230,11 @@ def run_backup(target: BackupItem) -> Optional[ResultInfo]: cmd.insert(3, "--exclude") cmd.insert(4, ",".join(target.excludes)) - print("Running target:", target.path) + logger.info(f"Running target: {target.path}") result = run(cmd, capture_output=True) if result.returncode != 0: - print("Result:", result.stderr.decode("utf-8")) + logger.error(f"Backup error: {result.stderr.decode('utf-8')}") raise Exception(result.stderr.decode("utf-8")) result_info = ResultInfo(**json.loads(result.stdout.decode("utf-8"))) @@ -239,5 +243,5 @@ def run_backup(target: BackupItem) -> Optional[ResultInfo]: return result_info except Exception as e: - print(f"Backup failed: {str(e)}") + logger.error(f"Backup failed: {str(e)}") return None diff --git a/src/logger.py b/src/logger.py new file mode 100644 index 0000000..d0c2b57 --- /dev/null +++ b/src/logger.py @@ -0,0 +1,11 @@ +from loguru import logger +from pathlib import Path +from sys import stdout + +LOGS_DIR = Path(__file__).parent / "logs" +if not LOGS_DIR.exists(): + LOGS_DIR.mkdir(parents=True) + +logger.remove() +logger.add(LOGS_DIR / "backupper.log", level="INFO", rotation="1 day", compression="gz") +logger.add(stdout, level="INFO", format="{message}") diff --git a/uv.lock b/uv.lock index bd853d0..26564d5 100644 --- a/uv.lock +++ b/uv.lock @@ -17,6 +17,7 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "click" }, + { name = "loguru" }, { name = "pydantic" }, { name = "typer" }, ] @@ -24,6 +25,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "click", specifier = "==8.1.3" }, + { name = "loguru", specifier = ">=0.7.3" }, { name = "pydantic", specifier = ">=2.11.4" }, { name = "typer", specifier = "==0.9.0" }, ] @@ -49,6 +51,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + [[package]] name = "pydantic" version = "2.11.4" @@ -139,3 +154,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846 wheels = [ { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, ] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +]