Coverage for seedboxsync/core/logger.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-31 17:10 +0000

1# 

2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr> 

3# 

4# For the full copyright and license information, please view the LICENSE 

5# file that was distributed with this source code. 

6"""Setup logger for Flask.""" 

7 

8from datetime import datetime 

9import logging 

10from typing import ClassVar 

11import click 

12from seedboxsync.core.database.models import Log 

13 

14 

15class DatabaseLogHandler(logging.Handler): 

16 """Persist log records using the application's Log model.""" 

17 

18 CLEANUP_INTERVAL = 500 

19 MAX_RECORDS = 1000 

20 

21 def emit(self, record: logging.LogRecord) -> None: 

22 """Write a formatted log record to the database.""" 

23 try: 

24 log_entry = Log.create( 

25 level=record.levelname, 

26 logger=record.name, 

27 module=record.module, 

28 message=record.getMessage(), 

29 timestamp=datetime.fromtimestamp(record.created), 

30 ) 

31 if log_entry.id % self.CLEANUP_INTERVAL == 0: 

32 latest_logs = Log.select(Log.id).order_by(Log.id.desc()).limit(self.MAX_RECORDS) 

33 Log.delete().where(Log.id.not_in(latest_logs)).execute() 

34 except Exception: 

35 self.handleError(record) 

36 

37 

38class ColorFormatter(logging.Formatter): 

39 """Format log records with colors based on their level.""" 

40 

41 COLORS: ClassVar[dict[int, str]] = { 

42 logging.DEBUG: "bright_black", 

43 logging.INFO: "blue", 

44 logging.WARNING: "yellow", 

45 logging.ERROR: "red", 

46 logging.CRITICAL: "bright_red", 

47 } 

48 

49 def format(self, record: logging.LogRecord) -> str: 

50 """ 

51 Format a log record and apply a color based on its log level. 

52 

53 Args: 

54 record: The log record to format. 

55 

56 Returns: 

57 str: The formatted and colorized log message. 

58 """ 

59 message = super().format(record) 

60 color = self.COLORS.get(record.levelno) 

61 

62 if color is None: 

63 return message 

64 

65 return click.style(message, fg=color) 

66 

67 

68def configure_logger(logger: logging.Logger) -> None: 

69 """ 

70 Configure the logger by applying the custom formatter to all existing handlers. 

71 

72 Args: 

73 logger: The logger to configure. 

74 """ 

75 formatter = ColorFormatter( 

76 fmt="%(asctime)s [%(levelname)-8s] [%(module)s] %(message)s", 

77 datefmt="%H:%M:%S", 

78 ) 

79 

80 for handler in logger.handlers: 

81 handler.setFormatter(formatter) 

82 

83 

84def add_database_log_handler(logger: logging.Logger) -> None: 

85 """ 

86 Add a handler that persists records to the database. 

87 

88 Args: 

89 logger: The logger to connect to the database log handler. 

90 """ 

91 for handler in [handler for handler in logger.handlers if isinstance(handler, DatabaseLogHandler)]: 

92 logger.removeHandler(handler) 

93 handler.close() 

94 

95 database_handler = DatabaseLogHandler() 

96 logger.addHandler(database_handler)