Hide keyboard shortcuts

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 

7 

8import sys 

9import logging 

10import traceback 

11from threading import Lock 

12 

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 

18 

19log = Logger("server") 

20 

21 

22""" 

23Mixin for servers that can receive and send logging packets 

24""" 

25class LoggingServer(StubServerMixin): 

26 

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 = {} 

35 

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) 

42 

43 def cleanup(self): 

44 self.stop_capturing_logging() 

45 

46 

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 } 

54 

55 

56 def cleanup_protocol(self, protocol): 

57 if protocol in self.logging_clients: 

58 del self.logging_clients[protocol] 

59 

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() 

65 

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() 

75 

76 

77 def start_capturing_logging(self): 

78 if not self.local_logging: 

79 self.local_logging = set_global_logging_handler(self.remote_logging_handler) 

80 

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) 

86 

87 

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 

161 

162 

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) 

171 

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) 

202 

203 def do_log(self, level, line): 

204 with self.logging_lock: 

205 log.log(level, line) 

206 

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)