rotate-log.py 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. #!/usr/bin/env python3
  2. from __future__ import annotations
  3. from datetime import datetime
  4. import os
  5. import sys
  6. from pathlib import Path
  7. def rotate(path: Path, backups: int) -> None:
  8. if backups <= 0:
  9. path.write_text("")
  10. return
  11. oldest = path.with_name(f"{path.name}.{backups}")
  12. if oldest.exists():
  13. oldest.unlink()
  14. for index in range(backups - 1, 0, -1):
  15. source = path.with_name(f"{path.name}.{index}")
  16. target = path.with_name(f"{path.name}.{index + 1}")
  17. if source.exists():
  18. source.replace(target)
  19. if path.exists():
  20. path.replace(path.with_name(f"{path.name}.1"))
  21. def main() -> int:
  22. if len(sys.argv) != 4:
  23. print("usage: rotate-log.py <log_path> <max_bytes> <backups>", file=sys.stderr)
  24. return 1
  25. log_path = Path(sys.argv[1])
  26. max_bytes = int(sys.argv[2])
  27. backups = int(sys.argv[3])
  28. log_path.parent.mkdir(parents=True, exist_ok=True)
  29. log_path.touch(exist_ok=True)
  30. stream = sys.stdin.buffer
  31. while True:
  32. chunk = stream.readline()
  33. if not chunk:
  34. break
  35. timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  36. text = chunk.decode(errors="replace").rstrip("\n")
  37. chunk = f"[{timestamp}] {text}\n".encode()
  38. with log_path.open("ab") as handle:
  39. handle.write(chunk)
  40. try:
  41. if log_path.stat().st_size > max_bytes:
  42. rotate(log_path, backups)
  43. log_path.touch(exist_ok=True)
  44. except FileNotFoundError:
  45. log_path.touch(exist_ok=True)
  46. return 0
  47. if __name__ == "__main__":
  48. raise SystemExit(main())