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,