Mercurial > libervia-backend
comparison sat/core/log_config.py @ 3280:96b9b65b4368
core (log): logging with Twisted now uses the new twisted.logger
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 25 May 2020 15:46:21 +0200 |
parents | 559a625a236b |
children | be6d91572633 |
comparison
equal
deleted
inserted
replaced
3279:8de63fe6b5c9 | 3280:96b9b65b4368 |
---|---|
27 class TwistedLogger(log.Logger): | 27 class TwistedLogger(log.Logger): |
28 colors = True | 28 colors = True |
29 force_colors = False | 29 force_colors = False |
30 | 30 |
31 def __init__(self, *args, **kwargs): | 31 def __init__(self, *args, **kwargs): |
32 super(TwistedLogger, self).__init__(*args, **kwargs) | 32 super().__init__(*args, **kwargs) |
33 from twisted.python import log as twisted_log | 33 from twisted.logger import Logger |
34 | 34 self.twisted_log = Logger() |
35 self.twisted_log = twisted_log | |
36 | 35 |
37 def out(self, message, level=None, **kwargs): | 36 def out(self, message, level=None, **kwargs): |
38 """Actually log the message | 37 """Actually log the message |
39 | 38 |
40 @param message: formatted message | 39 @param message: formatted message |
41 """ | 40 """ |
42 if kwargs.get('exc_info', False): | 41 if kwargs.pop('exc_info', False): |
43 message = self.addTraceback(message) | 42 message = self.addTraceback(message) |
44 self.twisted_log.msg( | 43 self.twisted_log.emit( |
45 message.encode("utf-8", "ignore"), sat_logged=True, level=level | 44 level=self.level_map[level], |
45 format=message, | |
46 sat_logged=True, | |
47 **kwargs, | |
46 ) | 48 ) |
47 | 49 |
48 | 50 |
49 class ConfigureBasic(log.ConfigureBase): | 51 class ConfigureBasic(log.ConfigureBase): |
50 def configureColors(self, colors, force_colors, levels_taints_dict): | 52 def configureColors(self, colors, force_colors, levels_taints_dict): |
105 | 107 |
106 | 108 |
107 class ConfigureTwisted(ConfigureBasic): | 109 class ConfigureTwisted(ConfigureBasic): |
108 LOGGER_CLASS = TwistedLogger | 110 LOGGER_CLASS = TwistedLogger |
109 | 111 |
110 def changeObserver(self, observer, can_colors=False): | |
111 """Install a hook on observer to manage SàT specificities | |
112 | |
113 @param observer: original observer to hook | |
114 @param can_colors: True if observer can display ansi colors | |
115 """ | |
116 | |
117 def observer_hook(event): | |
118 """redirect non SàT log to twisted_logger, and add colors when possible""" | |
119 if ( | |
120 "sat_logged" in event | |
121 ): # we only want our own logs, other are managed by twistedObserver | |
122 # we add colors if possible | |
123 if ( | |
124 can_colors and self.LOGGER_CLASS.colors | |
125 ) or self.LOGGER_CLASS.force_colors: | |
126 message = event.get("message", tuple()) | |
127 if message: | |
128 event["message"] = (b"".join(message),) # must be a tuple | |
129 observer(event) # we can now call the original observer | |
130 | |
131 return observer_hook | |
132 | |
133 def changeFileLogObserver(self, observer): | |
134 """Install SàT hook for FileLogObserver | |
135 | |
136 if the output is a tty, we allow colors, else we don't | |
137 @param observer: original observer to hook | |
138 """ | |
139 log_obs = observer.__self__ | |
140 log_file = log_obs.write.__self__ | |
141 try: | |
142 can_colors = log_file.isatty() | |
143 except AttributeError: | |
144 can_colors = False | |
145 return self.changeObserver(observer, can_colors=can_colors) | |
146 | |
147 def installObserverHook(self, observer): | |
148 """Check observer type and install SàT hook when possible | |
149 | |
150 @param observer: observer to hook | |
151 @return: hooked observer or original one | |
152 """ | |
153 if hasattr(observer, "__self__"): | |
154 ori = observer | |
155 if isinstance(observer.__self__, self.twisted_log.FileLogObserver): | |
156 observer = self.changeFileLogObserver(observer) | |
157 elif isinstance(observer.__self__, self.twisted_log.DefaultObserver): | |
158 observer = self.changeObserver(observer, can_colors=True) | |
159 else: | |
160 # we use print because log system is not fully initialized | |
161 print(("Unmanaged observer [%s]" % observer)) | |
162 return observer | |
163 self.observers[ori] = observer | |
164 return observer | |
165 | |
166 def preTreatment(self): | 112 def preTreatment(self): |
167 """initialise needed attributes, and install observers hooks""" | 113 from twisted import logger |
168 self.observers = {} | 114 global logger |
169 from twisted.python import log as twisted_log | 115 self.level_map = { |
170 | 116 C.LOG_LVL_DEBUG: logger.LogLevel.debug, |
171 self.twisted_log = twisted_log | 117 C.LOG_LVL_INFO: logger.LogLevel.info, |
172 self.log_publisher = twisted_log.msg.__self__ | 118 C.LOG_LVL_WARNING: logger.LogLevel.warn, |
173 | 119 C.LOG_LVL_ERROR: logger.LogLevel.error, |
174 def addObserverObserver(self_logpub, other): | 120 C.LOG_LVL_CRITICAL: logger.LogLevel.critical, |
175 """Install hook so we know when a new observer is added""" | 121 } |
176 other = self.installObserverHook(other) | 122 self.LOGGER_CLASS.level_map = self.level_map |
177 return self_logpub._originalAddObserver(other) | |
178 | |
179 def removeObserverObserver(self_logpub, ori): | |
180 """removeObserver hook fix | |
181 | |
182 As we wrap the original observer, the original removeObserver may want to remove the original object instead of the wrapper, this method fix this | |
183 """ | |
184 if ori in self.observers: | |
185 self_logpub._originalRemoveObserver(self.observers[ori]) | |
186 else: | |
187 try: | |
188 self_logpub._originalRemoveObserver(ori) | |
189 except ValueError: | |
190 try: | |
191 ori in self.cleared_observers | |
192 except AttributeError: | |
193 raise ValueError("Unknown observer") | |
194 | |
195 # we replace addObserver/removeObserver by our own | |
196 twisted_log.LogPublisher._originalAddObserver = ( | |
197 twisted_log.LogPublisher.addObserver | |
198 ) | |
199 twisted_log.LogPublisher._originalRemoveObserver = ( | |
200 twisted_log.LogPublisher.removeObserver | |
201 ) | |
202 import types # see https://stackoverflow.com/a/4267590 (thx Chris Morgan/aaronasterling) | |
203 | |
204 twisted_log.addObserver = types.MethodType( | |
205 addObserverObserver, self.log_publisher | |
206 ) | |
207 twisted_log.removeObserver = types.MethodType( | |
208 removeObserverObserver, self.log_publisher | |
209 ) | |
210 | |
211 # we now change existing observers | |
212 for idx, observer in enumerate(self.log_publisher.observers): | |
213 self.log_publisher.observers[idx] = self.installObserverHook(observer) | |
214 | 123 |
215 def configureLevel(self, level): | 124 def configureLevel(self, level): |
216 self.LOGGER_CLASS.level = level | 125 self.level = self.level_map[level] |
217 super(ConfigureTwisted, self).configureLevel(level) | |
218 | 126 |
219 def configureOutput(self, output): | 127 def configureOutput(self, output): |
220 import sys | 128 import sys |
129 from twisted.python import logfile | |
130 self.log_publisher = logger.LogPublisher() | |
221 | 131 |
222 if output is None: | 132 if output is None: |
223 output = C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT | 133 output = C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT |
224 self.manageOutputs(output) | 134 self.manageOutputs(output) |
225 addObserver = self.twisted_log.addObserver | |
226 | 135 |
227 if C.LOG_OPT_OUTPUT_DEFAULT in log.handlers: | 136 if C.LOG_OPT_OUTPUT_DEFAULT in log.handlers: |
228 # default output is already managed, we just add output to stdout if we are in debug or nodaemon mode | |
229 if self.backend_data is None: | 137 if self.backend_data is None: |
230 raise ValueError( | 138 raise ValueError( |
231 "You must pass options as backend_data with Twisted backend" | 139 "You must pass options as backend_data with Twisted backend" |
232 ) | 140 ) |
233 options = self.backend_data | 141 options = self.backend_data |
142 log_file = logfile.LogFile.fromFullPath(options['logfile']) | |
143 self.log_publisher.addObserver( | |
144 logger.FileLogObserver(log_file, self.textFormatter)) | |
145 # we also want output to stdout if we are in debug or nodaemon mode | |
234 if options.get("nodaemon", False) or options.get("debug", False): | 146 if options.get("nodaemon", False) or options.get("debug", False): |
235 addObserver(self.twisted_log.FileLogObserver(sys.stdout).emit) | 147 self.log_publisher.addObserver( |
236 else: | 148 logger.FileLogObserver(sys.stdout, self.textFormatter)) |
237 # \\default is not in the output, so we remove current observers | |
238 self.cleared_observers = self.log_publisher.observers | |
239 self.observers.clear() | |
240 del self.log_publisher.observers[:] | |
241 # and we forbid twistd to add any observer | |
242 self.twisted_log.addObserver = lambda other: None | |
243 | 149 |
244 if C.LOG_OPT_OUTPUT_FILE in log.handlers: | 150 if C.LOG_OPT_OUTPUT_FILE in log.handlers: |
245 from twisted.python import logfile | |
246 | 151 |
247 for path in log.handlers[C.LOG_OPT_OUTPUT_FILE]: | 152 for path in log.handlers[C.LOG_OPT_OUTPUT_FILE]: |
248 log_file = ( | 153 log_file = ( |
249 sys.stdout if path == "-" else logfile.LogFile.fromFullPath(path) | 154 sys.stdout if path == "-" else logfile.LogFile.fromFullPath(path) |
250 ) | 155 ) |
251 addObserver(self.twisted_log.FileLogObserver(log_file).emit) | 156 self.log_publisher.addObserver( |
157 logger.FileLogObserver(log_file, self.textFormatter)) | |
252 | 158 |
253 if C.LOG_OPT_OUTPUT_MEMORY in log.handlers: | 159 if C.LOG_OPT_OUTPUT_MEMORY in log.handlers: |
254 raise NotImplementedError( | 160 raise NotImplementedError( |
255 "Memory observer is not implemented in Twisted backend" | 161 "Memory observer is not implemented in Twisted backend" |
256 ) | 162 ) |
264 if force_colors and not colors: | 170 if force_colors and not colors: |
265 raise ValueError("colors must be True if force_colors is True") | 171 raise ValueError("colors must be True if force_colors is True") |
266 | 172 |
267 def postTreatment(self): | 173 def postTreatment(self): |
268 """Install twistedObserver which manage non SàT logs""" | 174 """Install twistedObserver which manage non SàT logs""" |
269 | 175 # from twisted import logger |
270 def twistedObserver(event): | 176 import sys |
271 """Observer which redirect log message not produced by SàT to SàT logging system""" | 177 filtering_obs = logger.FilteringLogObserver( |
272 if not "sat_logged" in event: | 178 observer=self.log_publisher, |
273 # this log was not produced by SàT | 179 predicates=[ |
274 from twisted.python import log as twisted_log | 180 logger.LogLevelFilterPredicate(self.level), |
275 | 181 ] |
276 text = twisted_log.textFromEventDict(event) | 182 ) |
277 if text is None: | 183 logger.globalLogBeginner.beginLoggingTo([filtering_obs]) |
278 return | 184 |
279 twisted_logger = log.getLogger(C.LOG_TWISTED_LOGGER) | 185 def textFormatter(self, event): |
280 log_method = ( | 186 if event.get('sat_logged', False): |
281 twisted_logger.error | 187 timestamp = ''.join([logger.formatTime(event.get("log_time", None)), " "]) |
282 if event.get("isError", False) | 188 return f"{timestamp}{event.get('log_format', '')}\n" |
283 else twisted_logger.info | 189 else: |
284 ) | 190 eventText = logger.eventAsText( |
285 log_method(text) | 191 event, includeSystem=True) |
286 | 192 if not eventText: |
287 self.log_publisher._originalAddObserver(twistedObserver) | 193 return None |
194 return eventText.replace("\n", "\n\t") + "\n" | |
288 | 195 |
289 | 196 |
290 class ConfigureStandard(ConfigureBasic): | 197 class ConfigureStandard(ConfigureBasic): |
291 def __init__( | 198 def __init__( |
292 self, | 199 self, |