Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/logging.py: 48%

67 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-16 22:16 +0000

1import logging 

2import os 

3 

4import google.cloud.logging 

5from google.cloud.logging import Resource 

6from google.cloud.logging.handlers import CloudLoggingHandler 

7from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS 

8 

9from viur.core import current 

10from viur.core.config import conf 

11 

12 

13# ViURDefaultLogger --------------------------------------------------------------------------------------------------- 

14 

15class ViURDefaultLogger(CloudLoggingHandler): 

16 """ 

17 This is the ViUR-customized CloudLoggingHandler 

18 """ 

19 

20 def emit(self, record: logging.LogRecord): 

21 message = super(ViURDefaultLogger, self).format(record) 

22 try: 

23 currentReq = current.request.get() 

24 TRACE = "projects/{}/traces/{}".format(client.project, currentReq._traceID) 

25 currentReq.maxLogLevel = max(currentReq.maxLogLevel, record.levelno) 

26 logID = currentReq.request.environ.get("HTTP_X_APPENGINE_REQUEST_LOG_ID") 

27 except: 

28 TRACE = None 

29 logID = None 

30 

31 self.transport.send( 

32 record, 

33 message, 

34 resource=self.resource, 

35 labels={ 

36 "project_id": conf.instance.project_id, 

37 "module_id": "default", 

38 "version_id": 

39 conf.instance.app_version 

40 if not conf.instance.is_dev_server 

41 else "dev_appserver", 

42 }, 

43 trace=TRACE, 

44 operation={ 

45 "first": False, 

46 "last": False, 

47 "id": logID 

48 } 

49 ) 

50 

51 

52# ViURLocalFormatter --------------------------------------------------------------------------------------------------- 

53 

54class ViURLocalFormatter(logging.Formatter): 

55 """ 

56 This is a formatter that injects console color sequences for debug output. 

57 

58 The formatting can be modified using environment variables as follows: 

59 

60 - VIUR_LOGGING_COLORIZATION can be either FULL (colorize full line) or DECENT (colorize debug level only) 

61 - VIUR_LOGGING_COLOR_DEBUG set debug level color 

62 - VIUR_LOGGING_COLOR_INFO set info level color 

63 - VIUR_LOGGING_COLOR_WARNING set warning level color 

64 - VIUR_LOGGING_COLOR_ERROR set error level color 

65 - VIUR_LOGGING_COLOR_CRITICAL set critical error level color 

66 

67 The colors can be "black", "red", "green", "yellow", "blue", "magenta", "cyan" and "white". 

68 

69 Example configuration using viur-cli 

70 ```sh 

71 VIUR_LOGGING_COLOR_WARNING=red VIUR_LOGGING_COLORIZATION=decent pipenv run viur run develop 

72 ``` 

73 

74 For details on console coloring, see https://en.wikipedia.org/wiki/ANSI_escape_code#Colors. 

75 """ 

76 COLORS = { 

77 name: idx for idx, name in enumerate(("BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE")) 

78 } 

79 

80 DEFAULTS = { 

81 "DEBUG": "CYAN", 

82 "INFO": "MAGENTA", 

83 "WARNING": "YELLOW", 

84 "ERROR": "RED", 

85 "CRITICAL": "RED", 

86 } 

87 

88 @staticmethod 

89 def colorize(level: str, text: str) -> str: 

90 """ 

91 Retrieving colors for given debug level, either from environment or by default. 

92 """ 

93 level = level.upper() 

94 color = os.getenv(f"VIUR_LOGGING_COLOR_{level}") or ViURLocalFormatter.DEFAULTS.get(level) 

95 color = ViURLocalFormatter.COLORS.get(color.upper(), 1) 

96 return f"\033[1;{color + 30}m{text}\033[0m" 

97 

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

99 # truncate the pathname 

100 if "/deploy" in record.pathname: 

101 pathname = record.pathname.split("/deploy/")[1] 

102 else: 

103 # When we have a module in a very deep package path, like 

104 # google.cloud.resourcemanager_v3.services.projects.client, 

105 # we turn it into something like google/.../projects/client. 

106 pathname = record.pathname 

107 if len(pathname) > 20: 

108 parts = pathname.split("/") 

109 del parts[1:-3] 

110 parts.insert(1, "...") 

111 pathname = "/".join(parts) 

112 

113 record.pathname = pathname 

114 

115 # Select colorization mode 

116 match (os.getenv(f"VIUR_LOGGING_COLORIZATION") or "FULL").upper(): 

117 case "DECENT": 

118 # In "decent" mode, just colorize the record level name 

119 record.levelname = ViURLocalFormatter.colorize(record.levelname, record.levelname) 

120 

121 case _: 

122 # Otherwise, colorize the entire debug output 

123 return ViURLocalFormatter.colorize(record.levelname, super().format(record)) 

124 

125 return super().format(record) 

126 

127 

128# Logger config 

129 

130client = google.cloud.logging.Client() 

131requestLogger = client.logger("ViUR") 

132requestLoggingRessource = Resource( 

133 type="gae_app", 

134 labels={ 

135 "project_id": conf.instance.project_id, 

136 "module_id": "default", 

137 "version_id": (conf.instance.app_version 

138 if not conf.instance.is_dev_server 

139 else "dev_appserver"), 

140 } 

141) 

142 

143logger = logging.getLogger() 

144logger.setLevel(logging.DEBUG) 

145 

146# Calling getLogger(name) ensures that any placeholder loggers held by loggerDict are fully initialized 

147# (https://stackoverflow.com/a/53250066) 

148for name, level in { 

149 k: v.getEffectiveLevel() 

150 for k, v in logging.root.manager.loggerDict.items() 

151 if isinstance(v, logging.Logger) 

152}.items(): 

153 logging.getLogger(name).setLevel(level) 

154 

155# Remove any existing handler from logger 

156for handler in logger.handlers[:]: 156 ↛ 157line 156 didn't jump to line 157 because the loop on line 156 never started

157 logger.removeHandler(handler) 

158 

159EXCLUDED_LOGGER_DEFAULTS = list(EXCLUDED_LOGGER_DEFAULTS) 

160EXCLUDED_LOGGER_DEFAULTS.append("google.cloud.logging_v2.handlers.transports.background_thread") 

161 

162# Disable internal logging 

163# https://github.com/googleapis/python-logging/issues/13#issuecomment-539723753 

164for logger_name in EXCLUDED_LOGGER_DEFAULTS: 

165 excluded_logger = logging.getLogger(logger_name) 

166 excluded_logger.propagate = False 

167 excluded_logger.addHandler(logging.NullHandler()) 

168 

169if not conf.instance.is_dev_server: 169 ↛ 176line 169 didn't jump to line 176 because the condition on line 169 was always true

170 # Plug-in ViURDefaultLogger 

171 handler = ViURDefaultLogger(client, name="ViUR-Messages", resource=Resource(type="gae_app", labels={})) 

172 logger.addHandler(handler) 

173 

174else: 

175 # Use ViURLocalFormatter for local debug message formatting 

176 formatter = ViURLocalFormatter(f"[%(asctime)s] %(pathname)s:%(lineno)d [%(levelname)s] %(message)s") 

177 sh = logging.StreamHandler() 

178 sh.setFormatter(formatter) 

179 logger.addHandler(sh)