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# 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. 

5 

6import sys 

7import logging 

8import traceback 

9from threading import Lock 

10 

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 

15 

16log = Logger("client") 

17 

18 

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

25 

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

35 

36 def init(self, opts): 

37 self.remote_logging = opts.remote_logging 

38 self.log_both = (opts.remote_logging or "").lower()=="both" 

39 

40 

41 def cleanup(self): 

42 ll = self.local_logging 

43 if ll: 

44 set_global_logging_handler(ll) 

45 

46 

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 

70 

71 def start_receiving_logging(self): 

72 self.add_packet_handler("logging", self._process_logging, False) 

73 self.send("logging-control", "start") 

74 

75 #def stop_receiving_logging(self): 

76 # self.send("logging-control", "stop") 

77 

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) 

102 

103 def do_log(self, level, line): 

104 with self.logging_lock: 

105 log.log(level, line) 

106 

107 

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