Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/mixins/remote_logging.py : 47%
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) 2010-2020 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6import sys
7import logging
8import traceback
9from threading import Lock
11from xpra.util import csv, typedict, repr_ellipsized
12from xpra.os_util import monotonic_time, strtobytes, bytestostr
13from xpra.client.mixins.stub_client_mixin import StubClientMixin
14from xpra.log import Logger, set_global_logging_handler
16log = Logger("client")
19"""
20Mixin for remote logging support,
21either sending local logging events to the server,
22or receiving logging events from the server.
23"""
24class RemoteLogging(StubClientMixin):
26 def __init__(self):
27 StubClientMixin.__init__(self)
28 self.remote_logging = False
29 self.in_remote_logging = False
30 self.local_logging = None
31 self.logging_lock = Lock()
32 self.log_both = False
33 self.request_server_log = False
34 self.monotonic_start_time = monotonic_time()
36 def init(self, opts):
37 self.remote_logging = opts.remote_logging
38 self.log_both = (opts.remote_logging or "").lower()=="both"
41 def cleanup(self):
42 ll = self.local_logging
43 if ll:
44 set_global_logging_handler(ll)
47 def parse_server_capabilities(self, c : typedict) -> bool:
48 if self.remote_logging.lower() in ("send", "both", "yes", "true", "on") and (
49 #'remote-logging.receive' was only added in v4.1 so check both:
50 c.boolget("remote-logging") or c.boolget("remote-logging.receive")):
51 #check for debug:
52 from xpra.log import is_debug_enabled
53 conflict = tuple(v for v in ("network", "crypto", "udp", "websocket") if is_debug_enabled(v))
54 if conflict:
55 log.warn("Warning: cannot enable remote logging")
56 log.warn(" because debug logging is enabled for: %s", csv(conflict))
57 return True
58 log.info("enabled remote logging")
59 if not self.log_both:
60 log.info(" see server log file for further output")
61 self.local_logging = set_global_logging_handler(self.remote_logging_handler)
62 elif self.remote_logging.lower()=="receive":
63 self.request_server_log = c.boolget("remote-logging.send")
64 if not self.request_server_log:
65 log.warn("Warning: cannot receive log output from the server")
66 log.warn(" the feature is not enabled or not supported by the server")
67 else:
68 self.after_handshake(self.start_receiving_logging)
69 return True
71 def start_receiving_logging(self):
72 self.add_packet_handler("logging", self._process_logging, False)
73 self.send("logging-control", "start")
75 #def stop_receiving_logging(self):
76 # self.send("logging-control", "stop")
78 def _process_logging(self, packet):
79 assert not self.local_logging, "cannot receive logging packets when forwarding logging!"
80 level, msg = packet[1:3]
81 prefix = "server: "
82 if len(packet)>=4:
83 dtime = packet[3]
84 prefix += "@%02i.%03i " % ((dtime//1000)%60, dtime%1000)
85 def dec(x):
86 try:
87 return x.decode("utf8")
88 except Exception:
89 return bytestostr(x)
90 try:
91 if isinstance(msg, (tuple, list)):
92 dmsg = " ".join(dec(x) for x in msg)
93 else:
94 dmsg = dec(msg)
95 for l in dmsg.splitlines():
96 self.do_log(level, prefix+l)
97 except Exception as e:
98 log("log message decoding error", exc_info=True)
99 log.error("Error: failed to parse logging message:")
100 log.error(" %s", repr_ellipsized(msg))
101 log.error(" %s", e)
103 def do_log(self, level, line):
104 with self.logging_lock:
105 log.log(level, line)
108 def remote_logging_handler(self, log, level, msg, *args, **kwargs):
109 #prevent loops (if our send call ends up firing another logging call):
110 if self.in_remote_logging:
111 return
112 self.in_remote_logging = True
113 def enc(x):
114 try:
115 return bytestostr(x).encode("utf8")
116 except UnicodeEncodeError:
117 return strtobytes(x)
118 def local_warn(*args):
119 self.local_logging(log, logging.WARNING, *args)
120 try:
121 dtime = int(1000*(monotonic_time() - self.monotonic_start_time))
122 if args:
123 s = msg % args
124 else:
125 s = msg
126 data = self.compressed_wrapper("text", enc(s), level=1)
127 self.send("logging", level, data, dtime)
128 exc_info = kwargs.get("exc_info")
129 if exc_info is True:
130 exc_info = sys.exc_info()
131 if exc_info and exc_info[0]:
132 for x in traceback.format_tb(exc_info[2]):
133 self.send("logging", level, enc(x), dtime)
134 try:
135 etypeinfo = exc_info[0].__name__
136 except AttributeError:
137 etypeinfo = str(exc_info[0])
138 self.send("logging", level, enc("%s: %s" % (etypeinfo, exc_info[1])), dtime)
139 if self.log_both:
140 self.local_logging(log, level, msg, *args, **kwargs)
141 except Exception as e:
142 if self.exit_code is not None:
143 #errors can happen during exit, don't care
144 return
145 local_warn("Warning: failed to send logging packet:")
146 local_warn(" %s" % e)
147 local_warn(" original unformatted message: %s", msg)
148 if args:
149 local_warn(" %i arguments: %s", len(args), args)
150 else:
151 local_warn(" (no arguments)")
152 try:
153 self.local_logging(log, level, msg, *args, **kwargs)
154 except Exception:
155 pass
156 try:
157 exc_info = sys.exc_info()
158 for x in traceback.format_tb(exc_info[2]):
159 for v in x.splitlines():
160 local_warn(v)
161 except Exception:
162 pass
163 finally:
164 self.in_remote_logging = False