Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/log.py : 78%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2012-2019 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7import os
8import sys
9import logging
10import weakref
11# This module is used by non-GUI programs and thus must not import gtk.
13LOG_PREFIX = os.environ.get("XPRA_LOG_PREFIX", "")
14LOG_FORMAT = os.environ.get("XPRA_LOG_FORMAT", "%(asctime)s %(message)s")
15NOPREFIX_FORMAT = "%(message)s"
17DEBUG_MODULES = os.environ.get("XPRA_DEBUG_MODULES", "").split(",")
19logging.basicConfig(format=LOG_FORMAT)
20logging.root.setLevel(logging.INFO)
22#so we can keep a reference to all the loggers in use
23#we may have multiple loggers for the same key, so use a dict
24#but we don't want to prevent garbage collection so use a list of weakrefs
25all_loggers = dict()
26def add_logger(categories, logger):
27 global all_loggers
28 categories = list(categories)
29 categories.append("all")
30 l = weakref.ref(logger)
31 for cat in categories:
32 all_loggers.setdefault(cat, set()).add(l)
34def get_all_loggers():
35 global all_loggers
36 a = set()
37 for loggers in all_loggers.values():
38 for logger in list(loggers):
39 #weakref:
40 instance = logger()
41 if instance:
42 a.add(instance)
43 return a
46debug_enabled_categories = set()
47debug_disabled_categories = set()
49def get_debug_args():
50 args = []
51 if debug_enabled_categories:
52 args += list(debug_enabled_categories)
53 if debug_disabled_categories:
54 for x in debug_disabled_categories:
55 args.append("-%s" % x)
56 return args
58class FullDebugContext:
60 def __enter__(self):
61 global debug_enabled_categories
62 self.debug_enabled_categories = debug_enabled_categories
63 debug_enabled_categories.clear()
64 debug_enabled_categories.add("all")
65 self.enabled = []
66 for x in get_all_loggers():
67 if not x.is_debug_enabled():
68 self.enabled.append(x)
69 x.enable_debug()
71 def __exit__(self, *_args):
72 for x in self.enabled:
73 x.disable_debug()
74 global debug_enabled_categories
75 debug_enabled_categories.clear()
76 debug_enabled_categories.add(self.debug_enabled_categories)
79def add_debug_category(*cat):
80 remove_disabled_category(*cat)
81 for c in cat:
82 debug_enabled_categories.add(c)
84def remove_debug_category(*cat):
85 for c in cat:
86 if c in debug_enabled_categories:
87 debug_enabled_categories.remove(c)
89def is_debug_enabled(category : str):
90 if "all" in debug_enabled_categories:
91 return True
92 if category in debug_enabled_categories:
93 return True
94 return isenvdebug(category) or isenvdebug("ALL")
97def add_disabled_category(*cat):
98 remove_debug_category(*cat)
99 for c in cat:
100 debug_disabled_categories.add(c)
102def remove_disabled_category(*cat):
103 for c in cat:
104 if c in debug_disabled_categories:
105 debug_disabled_categories.remove(c)
108def get_loggers_for_categories(*cat):
109 if not cat:
110 return []
111 if "all" in cat:
112 return get_all_loggers()
113 cset = set(cat)
114 matches = set()
115 for l in get_all_loggers():
116 if set(l.categories).issuperset(cset):
117 matches.add(l)
118 return list(matches)
120def enable_debug_for(*cat):
121 loggers = []
122 for l in get_loggers_for_categories(*cat):
123 if not l.is_debug_enabled():
124 l.enable_debug()
125 loggers.append(l)
126 return loggers
128def disable_debug_for(*cat):
129 loggers = []
130 for l in get_loggers_for_categories(*cat):
131 if l.is_debug_enabled():
132 l.disable_debug()
133 loggers.append(l)
134 return loggers
137default_level = logging.DEBUG
138def set_default_level(level):
139 global default_level
140 default_level = level
143def standard_logging(log, level, msg, *args, **kwargs):
144 #this is just the regular logging:
145 log(level, msg, *args, **kwargs)
147#this allows us to capture all logging and redirect it:
148#the default 'standard_logging' uses the logger,
149#but the client may inject its own handler here
150global_logging_handler = standard_logging
152def set_global_logging_handler(h):
153 assert callable(h)
154 global global_logging_handler
155 saved = global_logging_handler
156 global_logging_handler = h
157 return saved
160def setloghandler(lh):
161 logging.root.handlers = []
162 logging.root.addHandler(lh)
164def enable_color(to=sys.stdout, format_string=NOPREFIX_FORMAT):
165 if not hasattr(to, "fileno"):
166 #on win32 sys.stdout can be a "Blackhole",
167 #which does not have a fileno
168 return
169 from xpra.colorstreamhandler import ColorStreamHandler
170 csh = ColorStreamHandler(to)
171 csh.setFormatter(logging.Formatter(format_string))
172 setloghandler(csh)
174def enable_format(format_string):
175 try:
176 logging.root.handlers[0].formatter = logging.Formatter(format_string)
177 except (AttributeError, IndexError):
178 pass
181STRUCT_KNOWN_FILTERS = {
182 "Client" : {
183 "client" : "All client code",
184 "paint" : "Client window paint code",
185 "draw" : "Client draw packets",
186 "cairo" : "Cairo paint code used with the GTK3 client",
187 "opengl" : "Client OpenGL rendering",
188 "info" : "About and Session info dialogs",
189 "launcher" : "The client launcher program",
190 },
191 "General" : {
192 "clipboard" : "All clipboard operations",
193 "notify" : "Notification forwarding",
194 "tray" : "System Tray forwarding",
195 "printing" : "Printing",
196 "file" : "File transfers",
197 "keyboard" : "Keyboard mapping and key event handling",
198 "screen" : "Screen and workarea dimension",
199 "fps" : "Frames per second",
200 "xsettings" : "XSettings synchronization",
201 "dbus" : "DBUS calls",
202 "rpc" : "Remote Procedure Calls",
203 "menu" : "Menus",
204 "events" : "System and window events",
205 },
206 "Window" : {
207 "window" : "All window code",
208 "damage" : "Window X11 repaint events",
209 "geometry" : "Window geometry",
210 "shape" : "Window shape forwarding (XShape)",
211 "focus" : "Window focus",
212 "workspace" : "Window workspace synchronization",
213 "metadata" : "Window metadata",
214 "alpha" : "Window Alpha channel (transparency)",
215 "state" : "Window state",
216 "icon" : "Window icons",
217 "frame" : "Window frame",
218 "grab" : "Window grabs (both keyboard and mouse)",
219 "dragndrop" : "Window drag-n-drop events",
220 "filters" : "Window filters",
221 },
222 "Encoding" : {
223 "codec" : "Codec loader and video helper",
224 "loader" : "Pixel compression codec loader",
225 "video" : "Video encoding",
226 "score" : "Video pipeline scoring and selection",
227 "encoding" : "Server side encoding selection and compression",
228 "scaling" : "Picture scaling",
229 "scroll" : "Scrolling detection and compression",
230 "xor" : "XOR delta pre-compression",
231 "subregion" : "Video subregion processing",
232 "regiondetect" : "Video region detection",
233 "regionrefresh" : "Video region refresh",
234 "refresh" : "Refresh of lossy screen updates",
235 "compress" : "Pixel compression (non video)",
236 },
237 "Codec" : {
238 #codecs:
239 "csc" : "Colourspace conversion codecs",
240 "cuda" : "CUDA device access (nvenc)",
241 "cython" : "Cython CSC module",
242 "swscale" : "swscale CSC module",
243 "libyuv" : "libyuv CSC module",
244 "decoder" : "All decoders",
245 "encoder" : "All encoders",
246 "avcodec" : "avcodec decoder",
247 "libav" : "libav common code (used by swscale, avcodec and ffmpeg)",
248 "ffmpeg" : "ffmpeg encoder",
249 "pillow" : "Pillow encoder and decoder",
250 "jpeg" : "JPEG codec",
251 "vpx" : "libvpx encoder and decoder",
252 "nvenc" : "nvenc hardware encoder",
253 "nvfbc" : "nfbc screen capture",
254 "x264" : "libx264 encoder",
255 "x265" : "libx265 encoder",
256 "webp" : "libwebp encoder and decoder",
257 "webcam" : "webcam access",
258 },
259 "Pointer" : {
260 "mouse" : "Mouse motion",
261 "cursor" : "Mouse cursor shape",
262 },
263 "Misc" : {
264 #libraries
265 "gtk" : "All GTK code: bindings, client, etc",
266 "util" : "All utility functions",
267 "gobject" : "Command line clients",
268 #server bits:
269 "test" : "Test code",
270 "verbose" : "Very verbose flag",
271 #specific applications:
272 },
273 "Network" : {
274 #internal / network:
275 "network" : "All network code",
276 "bandwidth" : "Bandwidth detection and management",
277 "ssh" : "SSH connections",
278 "ssl" : "SSL connections",
279 "http" : "HTTP requests",
280 "rfb" : "RFB Protocol",
281 "mmap" : "mmap transfers",
282 "protocol" : "Packet input and output (formatting, parsing, sending and receiving)",
283 "websocket" : "WebSocket layer",
284 "named-pipe" : "Named pipe",
285 "udp" : "UDP",
286 "crypto" : "Encryption",
287 "auth" : "Authentication",
288 "upnp" : "UPnP",
289 },
290 "Server" : {
291 #Server:
292 "server" : "All server code",
293 "proxy" : "Proxy server",
294 "shadow" : "Shadow server",
295 "command" : "Server control channel",
296 "timeout" : "Server timeouts",
297 "exec" : "Executing commands",
298 #server features:
299 "mdns" : "mDNS session publishing",
300 #server internals:
301 "stats" : "Server statistics",
302 "xshm" : "XShm pixel capture",
303 },
304 "Sound" : {
305 "sound" : "All sound",
306 "gstreamer" : "GStreamer internal messages",
307 "av-sync" : "Audio-video sync",
308 },
309 "X11" : {
310 "x11" : "All X11 code",
311 "xinput" : "XInput bindings",
312 "bindings" : "X11 Cython bindings",
313 "core" : "X11 core bindings",
314 "randr" : "X11 RandR bindings",
315 "ximage" : "X11 XImage bindings",
316 "error" : "X11 errors",
317 },
318 "Platform" : {
319 "platform" : "All platform support code",
320 "import" : "Platform support import code",
321 "osx" : "Mac OS X platform support code",
322 "win32" : "Microsoft Windows platform support code",
323 "posix" : "Posix platform code",
324 },
325 }
327#flatten it:
328KNOWN_FILTERS = {}
329for d in STRUCT_KNOWN_FILTERS.values():
330 for k,v in d.items():
331 KNOWN_FILTERS[k] = v
334def isenvdebug(category : str) -> bool:
335 return os.environ.get("XPRA_%s_DEBUG" % category.upper().replace("-", "_").replace("+", "_"), "0")=="1"
337# A wrapper around 'logging' with some convenience stuff. In particular:
338# -- You initialize it with a list of categories
339# If unset, the default logging target is set to the name of the module where
340# Logger() was called.
341# -- Any of the categories can enable debug logging if the environment
342# variable 'XPRA_${CATEGORY}_DEBUG' is set to "1"
343# -- We also keep a list of debug_categories, so these can get enabled
344# programatically too
345# -- We keep track of which loggers are associated with each category,
346# so we can enable/disable debug logging by category
347# -- You can pass exc_info=True to any method, and sys.exc_info() will be
348# substituted.
349# -- __call__ is an alias for debug
350# -- we bypass the logging system unless debugging is enabled for the logger,
351# which is much faster than relying on the python logging code
353class Logger:
354 def __init__(self, *categories):
355 global default_level, debug_disabled_categories, KNOWN_FILTERS
356 self.categories = list(categories)
357 try:
358 caller = sys._getframe(1).f_globals["__name__"] #pylint: disable=protected-access
359 except AttributeError:
360 caller = None
361 if caller not in ("__main__", None):
362 self.categories.insert(0, caller)
363 self.logger = logging.getLogger(".".join(self.categories))
364 self.logger.setLevel(default_level)
365 disabled = False
366 enabled = False
367 if caller in DEBUG_MODULES:
368 enabled = True
369 else:
370 for cat in self.categories:
371 if cat in debug_disabled_categories:
372 disabled = True
373 if is_debug_enabled(cat):
374 enabled = True
375 if len(categories)>1:
376 #try all string permutations of those categories:
377 # "keyboard", "events" -> "keyboard+events" or "events+keyboard"
378 import itertools
379 for cats in itertools.permutations(categories):
380 cstr = "+".join(cats)
381 if cstr in debug_disabled_categories:
382 disabled = True
383 if is_debug_enabled(cstr):
384 enabled = True
385 self.debug_enabled = enabled and not disabled
386 #ready, keep track of it:
387 add_logger(self.categories, self)
388 for x in categories:
389 if x not in KNOWN_FILTERS:
390 self.warn("unknown logging category: %s", x)
392 def get_info(self) -> dict:
393 return {
394 "categories" : self.categories,
395 "debug" : self.debug_enabled,
396 "level" : self.logger.getEffectiveLevel(),
397 }
399 def __repr__(self):
400 return "Logger(%s)" % ", ".join(self.categories)
402 def is_debug_enabled(self) -> bool:
403 return self.debug_enabled
405 def enable_debug(self):
406 self.debug_enabled = True
408 def disable_debug(self):
409 self.debug_enabled = False
412 def log(self, level, msg : str, *args, **kwargs):
413 if kwargs.get("exc_info") is True:
414 ei = sys.exc_info()
415 if ei!=(None, None, None):
416 kwargs["exc_info"] = ei
417 global global_logging_handler
418 if LOG_PREFIX:
419 msg = LOG_PREFIX+msg
420 global_logging_handler(self.logger.log, level, msg, *args, **kwargs)
422 def __call__(self, msg : str, *args, **kwargs):
423 if self.debug_enabled:
424 self.log(logging.DEBUG, msg, *args, **kwargs)
425 def debug(self, msg : str, *args, **kwargs):
426 if self.debug_enabled:
427 self.log(logging.DEBUG, msg, *args, **kwargs)
428 def info(self, msg : str, *args, **kwargs):
429 self.log(logging.INFO, msg, *args, **kwargs)
430 def warn(self, msg : str, *args, **kwargs):
431 self.log(logging.WARN, msg, *args, **kwargs)
432 def error(self, msg : str, *args, **kwargs):
433 self.log(logging.ERROR, msg, *args, **kwargs)
436class CaptureHandler(logging.Handler):
438 def __init__(self):
439 super().__init__(logging.DEBUG)
440 self.records = []
442 def handle(self, record):
443 self.records.append(record)
445 def emit(self, record):
446 self.records.append(record)
448 def createLock(self):
449 self.lock = None
451class SIGPIPEStreamHandler(logging.StreamHandler):
452 def flush(self):
453 try:
454 super().flush()
455 except BrokenPipeError:
456 pass
458 def emit(self, record):
459 try:
460 msg = self.format(record)
461 stream = self.stream
462 # issue 35046: merged two stream.writes into one.
463 stream.write(msg + self.terminator)
464 self.flush()
465 except RecursionError: # See issue 36272
466 raise
467 except BrokenPipeError:
468 pass
469 except Exception:
470 self.handleError(record)