Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/logging_server.py : 45%
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# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2010-2020 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.
6#pylint: disable-msg=E1101
8import sys
9import logging
10import traceback
11from threading import Lock
13from xpra.os_util import bytestostr, strtobytes, monotonic_time
14from xpra.util import repr_ellipsized
15from xpra.scripts.config import FALSE_OPTIONS, TRUE_OPTIONS
16from xpra.server.mixins.stub_server_mixin import StubServerMixin
17from xpra.log import Logger, set_global_logging_handler
19log = Logger("server")
22"""
23Mixin for servers that can receive and send logging packets
24"""
25class LoggingServer(StubServerMixin):
27 def __init__(self):
28 self.remote_logging_send = False
29 self.remote_logging_receive = False
30 self.logging_lock = Lock()
31 self.log_both = False
32 self.in_remote_logging = False
33 self.local_logging = None
34 self.logging_clients = {}
36 def init(self, opts):
37 self.log_both = (opts.remote_logging or "").lower()=="both"
38 if opts.remote_logging.lower() not in FALSE_OPTIONS:
39 self.remote_logging_send = opts.remote_logging.lower() in ("allow", "send", "both")
40 #"yes" is here for backwards compatibility:
41 self.remote_logging_receive = opts.remote_logging.lower() in ["allow", "receive", "both"]+list(TRUE_OPTIONS)
43 def cleanup(self):
44 self.stop_capturing_logging()
47 def get_server_features(self, _source=None) -> dict:
48 return {
49 "remote-logging" : self.remote_logging_receive, #pre-v4.1 feature name
50 "remote-logging.receive" : self.remote_logging_receive,
51 "remote-logging.multi-line" : True,
52 "remote-logging.send" : self.remote_logging_send,
53 }
56 def cleanup_protocol(self, protocol):
57 if protocol in self.logging_clients:
58 del self.logging_clients[protocol]
60 def remove_logging_client(self, protocol):
61 if self.logging_clients.pop(protocol, None) is None:
62 log.warn("Warning: logging was not enabled for '%r'", protocol)
63 if not self.logging_clients:
64 self.stop_capturing_logging()
66 def add_logging_client(self, protocol):
67 n = len(self.logging_clients)
68 if protocol in self.logging_clients:
69 log.warn("Warning: logging already enabled for client %s", protocol)
70 else:
71 log.info("sending log output to %s", protocol)
72 self.logging_clients[protocol] = monotonic_time()
73 if n==0:
74 self.start_capturing_logging()
77 def start_capturing_logging(self):
78 if not self.local_logging:
79 self.local_logging = set_global_logging_handler(self.remote_logging_handler)
81 def stop_capturing_logging(self):
82 ll = self.local_logging
83 if ll:
84 self.local_logging = None
85 set_global_logging_handler(ll)
88 def remote_logging_handler(self, log, level, msg, *args, **kwargs):
89 #prevent loops (if our send call ends up firing another logging call):
90 if self.in_remote_logging:
91 return
92 assert self.local_logging
93 def enc(x):
94 try:
95 return bytestostr(x).encode("utf8")
96 except UnicodeEncodeError:
97 return strtobytes(x)
98 def local_warn(*args):
99 self.local_logging(log, logging.WARNING, *args)
100 def local_err(message):
101 if self._closing:
102 return
103 local_warn("Warning: %s:", message)
104 local_warn(" %s" % e)
105 local_warn(" original unformatted message: %s", msg)
106 if args:
107 local_warn(" %i arguments: %s", len(args), args)
108 else:
109 local_warn(" (no arguments)")
110 try:
111 self.local_logging(log, level, msg, *args, **kwargs)
112 except Exception:
113 pass
114 try:
115 exc_info = sys.exc_info()
116 for x in traceback.format_tb(exc_info[2]):
117 for v in x.splitlines():
118 local_warn(v)
119 except Exception:
120 pass
121 self.in_remote_logging = True
122 try:
123 try:
124 if args:
125 s = msg % args
126 else:
127 s = msg
128 except Exception as e:
129 local_err("failed to format log message")
130 return
131 for proto, start_time in self.logging_clients.items():
132 source = self.get_server_source(proto)
133 if not source:
134 continue
135 try:
136 dtime = int(1000*(monotonic_time() - start_time))
137 data = source.compressed_wrapper("text", enc(s))
138 source.send("logging", level, data, dtime)
139 exc_info = kwargs.get("exc_info")
140 if exc_info is True:
141 exc_info = sys.exc_info()
142 if exc_info and exc_info[0]:
143 for x in traceback.format_tb(exc_info[2]):
144 self.send("logging", level, enc(x), dtime)
145 try:
146 etypeinfo = exc_info[0].__name__
147 except AttributeError:
148 etypeinfo = str(exc_info[0])
149 source.send("logging", level, enc("%s: %s" % (etypeinfo, exc_info[1])), dtime)
150 except Exception as e:
151 if self._closing:
152 return
153 local_warn("Warning: failed to send log message to %s", source)
154 if self.log_both:
155 try:
156 self.local_logging(log, level, msg, *args, **kwargs)
157 except Exception:
158 pass
159 finally:
160 self.in_remote_logging = False
163 def _process_logging_control(self, proto, packet):
164 action = bytestostr(packet[1])
165 if action=="start":
166 self.add_logging_client(proto)
167 elif action=="stop":
168 self.remove_logging_client(proto)
169 else:
170 log.warn("Warning: unknown logging-control action '%r'", action)
172 def _process_logging(self, proto, packet):
173 assert self.remote_logging_receive
174 ss = self.get_server_source(proto)
175 if ss is None:
176 return
177 level, msg = packet[1:3]
178 prefix = "client "
179 counter = getattr(ss, "counter", 0)
180 if counter>0:
181 prefix += "%3i " % counter
182 if len(packet)>=4:
183 dtime = packet[3]
184 prefix += "@%02i.%03i " % ((dtime//1000)%60, dtime%1000)
185 def dec(x):
186 try:
187 return x.decode("utf8")
188 except Exception:
189 return bytestostr(x)
190 try:
191 if isinstance(msg, (tuple, list)):
192 dmsg = " ".join(dec(x) for x in msg)
193 else:
194 dmsg = dec(msg)
195 for l in dmsg.splitlines():
196 self.do_log(level, prefix+l)
197 except Exception as e:
198 log("log message decoding error", exc_info=True)
199 log.error("Error: failed to parse logging message:")
200 log.error(" %s", repr_ellipsized(msg))
201 log.error(" %s", e)
203 def do_log(self, level, line):
204 with self.logging_lock:
205 log.log(level, line)
207 def init_packet_handlers(self):
208 if self.remote_logging_receive:
209 self.add_packet_handler("logging", self._process_logging, False)
210 if self.remote_logging_send:
211 self.add_packet_handler("logging-control", self._process_logging_control, False)