Coverage for src/inheritance_calculator_core/utils/logger.py: 0%

1"""

2ログ管理モジュール

3

4アプリケーション全体で使用するロガーを提供します。

5"""

6

7 import logging

8 import sys

9 from logging.handlers import RotatingFileHandler

10 from pathlib import Path

11 from typing import Optional

12

13 from .config import settings

14 from .exceptions import LoggingError

15

16

17 def setup_logger(

18 name: str,

19 log_file: Optional[Path] = None,

20 level: Optional[str] = None,

21) -> logging.Logger:

22 """

23 ロガーをセットアップします。

24

25 Args:

26 name: ロガー名

27 log_file: ログファイルパス（Noneの場合は設定ファイルから取得）

28 level: ログレベル（Noneの場合は設定ファイルから取得）

29

30 Returns:

31 設定済みのロガー

32

33 Raises:

34 LoggingError: ロガーのセットアップに失敗した場合

35 """

36 try:

37 logger = logging.getLogger(name)

38

39 # 既にハンドラが設定されている場合はクリア（再設定可能に）

40 if logger.handlers:

41 logger.handlers.clear()

42

43 # ログレベルの設定

44 try:

45 log_level = level or (settings.log.level if settings else "INFO")

46 logger.setLevel(getattr(logging, log_level.upper()))

47 except AttributeError:

48 # settingsがNoneの場合のフォールバック

49 logger.setLevel(logging.INFO)

50 log_level = "INFO"

51

52 # フォーマッタの設定

53 formatter = logging.Formatter(

54 fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",

55 datefmt="%Y-%m-%d %H:%M:%S",

56 )

57

58 # コンソールハンドラの設定

59 console_handler = logging.StreamHandler(sys.stdout)

60 console_handler.setLevel(logging.INFO)

61 console_handler.setFormatter(formatter)

62 logger.addHandler(console_handler)

63

64 # ファイルハンドラの設定（settingsがある場合のみ）

65 if log_file is None and settings:

66 try:

67 log_file = settings.logs_dir / settings.log.file.split("/")[-1]

68 except AttributeError:

69 # settingsの属性にアクセスできない場合はファイルハンドラをスキップ

70 log_file = None

71

72 if log_file is not None:

73 try:

74 log_file.parent.mkdir(parents=True, exist_ok=True)

75 except OSError as e:

76 raise LoggingError(f"Failed to create log directory: {log_file.parent}") from e

77

78 try:

79 max_bytes = settings.log.max_bytes if settings else 10485760

80 backup_count = settings.log.backup_count if settings else 5

81

82 file_handler = RotatingFileHandler(

83 log_file,

84 maxBytes=max_bytes,

85 backupCount=backup_count,

86 encoding="utf-8",

87 )

88 file_handler.setLevel(getattr(logging, log_level.upper()))

89 file_handler.setFormatter(formatter)

90 logger.addHandler(file_handler)

91 except OSError as e:

92 raise LoggingError(f"Failed to create log file handler: {log_file}") from e

93

94 # プロパゲーションを無効化（重複ログ防止）

95 logger.propagate = False

96

97 return logger

98

99 except Exception as e:

100 if isinstance(e, LoggingError):

101 raise

102 raise LoggingError(f"Failed to setup logger '{name}'") from e

103

104

105# ロガーキャッシュ

106 _logger_cache: dict[str, logging.Logger] = {}

107

108# デフォルトロガー

109 default_logger = setup_logger("inheritance_calculator")

110 _logger_cache["inheritance_calculator"] = default_logger

111

112

113 def get_logger(name: str) -> logging.Logger:

114 """

115 指定された名前のロガーを取得します（キャッシュ付き）。

116

117 Args:

118 name: ロガー名

119

120 Returns:

121 ロガー

122 """

123 if name not in _logger_cache:

124 _logger_cache[name] = setup_logger(name)

125 return _logger_cache[name]