Coverage for yasmon/loggers.py: 100%

87 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-28 10:57 +0000

1from loguru import logger 

2from abc import ABC, abstractmethod 

3import sys 

4import yaml 

5import systemd.journal 

6 

7 

8class LoggerSyntaxError(Exception): 

9 """ 

10 Raised on logger syntax error 

11 """ 

12 

13 def __init__(self, message="logger syntax error"): 

14 self.message = message 

15 super().__init__(self.message) 

16 

17 

18class AbstractLogger(ABC): 

19 """ 

20 Abstract class from which all logger classes are derived. 

21 

22 The preferred way to instatiate a logger is from class 

23 method :func:`~from_yaml`. 

24 """ 

25 

26 @abstractmethod 

27 def __init__(self): 

28 self.check_imp_level() 

29 self.id = self.add_logger() 

30 logger.info(f'logger ({self.__class__}) initialized') 

31 

32 @classmethod 

33 @abstractmethod 

34 def from_yaml(cls, data: str): 

35 """ 

36 A class method for constructing a logger from a YAML document. 

37 

38 :param data: yaml data 

39 

40 :return: new instance 

41 """ 

42 

43 logger.debug(f'logger defined form yaml \n{data}') 

44 

45 def add_logger(self): 

46 """ 

47 Add logger with logging level. 

48 

49 :param level: logging level (lower case) 

50 """ 

51 

52 return logger.add(self.handler, 

53 format=self.format, 

54 level=self.level.upper()) 

55 

56 def check_imp_level(self): 

57 imp_levels = [ 

58 'trace', 

59 'debug', 

60 'info', 

61 'success', 

62 'warning', 

63 'error', 

64 'critical' 

65 ] 

66 

67 if self.level not in imp_levels: 

68 raise LoggerSyntaxError(f"""\ 

69 in {self.__class__} invalid logger level {self.level} 

70 """) 

71 

72 

73class StdErrLogger(AbstractLogger): 

74 """ 

75 `stderr` logger. 

76 

77 The preferred way to instatiate is from class 

78 method :func:`~from_yaml`. 

79 """ 

80 

81 def __init__(self, level: str = 'debug', format: str = ( 

82 "<level>{level: <8}</level> | " 

83 "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | " # noqa 

84 "<level>{message}</level>" 

85 )): 

86 self.handler = sys.stderr 

87 self.level = level 

88 self.format = format 

89 super().__init__() 

90 

91 @classmethod 

92 def from_yaml(cls, data: str): 

93 """ 

94 A class method for constructing a stderr logger from a YAML document. 

95 

96 :param data: yaml data 

97 

98 :return: new instance 

99 """ 

100 

101 try: 

102 parsed = yaml.safe_load(data) 

103 super().from_yaml(data) 

104 except yaml.YAMLError as err: 

105 raise LoggerSyntaxError(err) 

106 

107 if parsed is not None and 'level' in parsed: 

108 if isinstance(parsed['level'], str): 

109 return cls(parsed['level']) 

110 else: 

111 raise LoggerSyntaxError('in log_stderr expected ' 

112 'level to be of type str') 

113 else: 

114 return cls() 

115 

116 

117class FileLogger(AbstractLogger): 

118 """ 

119 Logger logging into a file 

120 

121 The preferred way to instatiate is from class 

122 method :func:`~from_yaml`. 

123 """ 

124 

125 def __init__(self, path: str, level: str = 'debug', 

126 format: str = ("<level>{level: <8}</level> | " 

127 "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | " # noqa 

128 "<level>{message}</level>")): 

129 self.handler = path 

130 self.path = path 

131 self.level = level 

132 

133 self.format = format 

134 super().__init__() 

135 

136 @classmethod 

137 def from_yaml(cls, data: str): 

138 """ 

139 A class method for constructing a stderr logger from a YAML document. 

140 

141 :param data: yaml data 

142 

143 :return: new instance 

144 """ 

145 

146 try: 

147 parsed = yaml.safe_load(data) 

148 super().from_yaml(data) 

149 except yaml.YAMLError as err: 

150 raise LoggerSyntaxError(err) 

151 

152 if parsed is not None and 'path' in parsed: 

153 path = parsed['path'] 

154 if not isinstance(path, str): 

155 raise LoggerSyntaxError('in log_file expected ' 

156 'path to be of type str') 

157 else: 

158 raise LoggerSyntaxError('in logger log_file path is missing') 

159 

160 if parsed is not None and 'level' in parsed: 

161 level = parsed['level'] 

162 if not isinstance(level, str): 

163 raise LoggerSyntaxError('in log_file expected ' 

164 'level to be of type str') 

165 

166 return cls(path, level) 

167 

168 return cls(path) 

169 

170 

171class JournalLogger(AbstractLogger): 

172 """ 

173 System journal logger. 

174 

175 The preferred way to instatiate is from class 

176 method :func:`~from_yaml`. 

177 """ 

178 

179 def __init__(self, level: str = 'debug', format: str = ( 

180 "<level>{level: <8}</level> | " 

181 "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | " # noqa 

182 "<level>{message}</level>" 

183 )): 

184 self.handler = systemd.journal.JournalHandler() 

185 self.level = level 

186 self.format = format 

187 super().__init__() 

188 

189 @classmethod 

190 def from_yaml(cls, data: str): 

191 """ 

192 A class method for constructing a system journal 

193 logger from a YAML document. 

194 

195 :param data: yaml data 

196 

197 :return: new instance 

198 """ 

199 

200 try: 

201 parsed = yaml.safe_load(data) 

202 super().from_yaml(data) 

203 except yaml.YAMLError as err: 

204 raise LoggerSyntaxError(f'in log_journal {err}') 

205 

206 if parsed is not None and 'level' in parsed: 

207 level = parsed['level'] 

208 if not isinstance(level, str): 

209 raise LoggerSyntaxError('in log_journal expected ' 

210 'level to be of type str') 

211 else: 

212 return cls(level) 

213 

214 return cls()