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
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
1import logging
2import os
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
9from viur.core import current
10from viur.core.config import conf
13# ViURDefaultLogger ---------------------------------------------------------------------------------------------------
15class ViURDefaultLogger(CloudLoggingHandler):
16 """
17 This is the ViUR-customized CloudLoggingHandler
18 """
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
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 )
52# ViURLocalFormatter ---------------------------------------------------------------------------------------------------
54class ViURLocalFormatter(logging.Formatter):
55 """
56 This is a formatter that injects console color sequences for debug output.
58 The formatting can be modified using environment variables as follows:
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
67 The colors can be "black", "red", "green", "yellow", "blue", "magenta", "cyan" and "white".
69 Example configuration using viur-cli
70 ```sh
71 VIUR_LOGGING_COLOR_WARNING=red VIUR_LOGGING_COLORIZATION=decent pipenv run viur run develop
72 ```
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 }
80 DEFAULTS = {
81 "DEBUG": "CYAN",
82 "INFO": "MAGENTA",
83 "WARNING": "YELLOW",
84 "ERROR": "RED",
85 "CRITICAL": "RED",
86 }
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"
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)
113 record.pathname = pathname
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)
121 case _:
122 # Otherwise, colorize the entire debug output
123 return ViURLocalFormatter.colorize(record.levelname, super().format(record))
125 return super().format(record)
128# Logger config
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)
143logger = logging.getLogger()
144logger.setLevel(logging.DEBUG)
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)
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)
159EXCLUDED_LOGGER_DEFAULTS = list(EXCLUDED_LOGGER_DEFAULTS)
160EXCLUDED_LOGGER_DEFAULTS.append("google.cloud.logging_v2.handlers.transports.background_thread")
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())
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)
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)