1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# The source code is from following Python documentation: 3*9c5db199SXin Li# https://docs.python.org/2/howto/logging-cookbook.html#network-logging 4*9c5db199SXin Li 5*9c5db199SXin Li# Classes in this file are used to create a simple TCP socket-based logging 6*9c5db199SXin Li# receiver. The receiver listens to default logging port (9020) and save log to 7*9c5db199SXin Li# any given log configuration, e.g., a local file. Once the receiver is running, 8*9c5db199SXin Li# client can add a logging handler to write log to the receiver with following 9*9c5db199SXin Li# sample code: 10*9c5db199SXin Li# socketHandler = logging.handlers.SocketHandler('localhost', 11*9c5db199SXin Li# logging.handlers.DEFAULT_TCP_LOGGING_PORT) 12*9c5db199SXin Li# logging.getLogger().addHandler(socketHandler) 13*9c5db199SXin Li 14*9c5db199SXin Lifrom __future__ import absolute_import 15*9c5db199SXin Lifrom __future__ import division 16*9c5db199SXin Lifrom __future__ import print_function 17*9c5db199SXin Li 18*9c5db199SXin Liimport ctypes 19*9c5db199SXin Liimport pickle 20*9c5db199SXin Liimport logging 21*9c5db199SXin Liimport multiprocessing 22*9c5db199SXin Liimport select 23*9c5db199SXin Liimport six.moves.socketserver 24*9c5db199SXin Liimport struct 25*9c5db199SXin Liimport time 26*9c5db199SXin Li 27*9c5db199SXin Liimport common 28*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils 29*9c5db199SXin Li 30*9c5db199SXin Liclass LogRecordStreamHandler(six.moves.socketserver.StreamRequestHandler): 31*9c5db199SXin Li """Handler for a streaming logging request. 32*9c5db199SXin Li 33*9c5db199SXin Li This basically logs the record using whatever logging policy is 34*9c5db199SXin Li configured locally. 35*9c5db199SXin Li """ 36*9c5db199SXin Li 37*9c5db199SXin Li def handle(self): 38*9c5db199SXin Li """ 39*9c5db199SXin Li Handle multiple requests - each expected to be a 4-byte length, 40*9c5db199SXin Li followed by the LogRecord in pickle format. Logs the record 41*9c5db199SXin Li according to whatever policy is configured locally. 42*9c5db199SXin Li """ 43*9c5db199SXin Li while True: 44*9c5db199SXin Li chunk = self.connection.recv(4) 45*9c5db199SXin Li if len(chunk) < 4: 46*9c5db199SXin Li return 47*9c5db199SXin Li slen = struct.unpack('>L', chunk)[0] 48*9c5db199SXin Li chunk = self.connection.recv(slen) 49*9c5db199SXin Li while len(chunk) < slen: 50*9c5db199SXin Li chunk = chunk + self.connection.recv(slen - len(chunk)) 51*9c5db199SXin Li obj = self.unpickle(chunk) 52*9c5db199SXin Li record = logging.makeLogRecord(obj) 53*9c5db199SXin Li self.handle_log_record(record) 54*9c5db199SXin Li 55*9c5db199SXin Li 56*9c5db199SXin Li def unpickle(self, data): 57*9c5db199SXin Li """Unpickle data received. 58*9c5db199SXin Li 59*9c5db199SXin Li @param data: Received data. 60*9c5db199SXin Li @returns: unpickled data. 61*9c5db199SXin Li """ 62*9c5db199SXin Li return pickle.loads(data) 63*9c5db199SXin Li 64*9c5db199SXin Li 65*9c5db199SXin Li def handle_log_record(self, record): 66*9c5db199SXin Li """Process log record. 67*9c5db199SXin Li 68*9c5db199SXin Li @param record: log record. 69*9c5db199SXin Li """ 70*9c5db199SXin Li # if a name is specified, we use the named logger rather than the one 71*9c5db199SXin Li # implied by the record. 72*9c5db199SXin Li if self.server.logname is not None: 73*9c5db199SXin Li name = self.server.logname 74*9c5db199SXin Li else: 75*9c5db199SXin Li name = record.name 76*9c5db199SXin Li logger = logging.getLogger(name) 77*9c5db199SXin Li # N.B. EVERY record gets logged. This is because Logger.handle 78*9c5db199SXin Li # is normally called AFTER logger-level filtering. If you want 79*9c5db199SXin Li # to do filtering, do it at the client end to save wasting 80*9c5db199SXin Li # cycles and network bandwidth! 81*9c5db199SXin Li logger.handle(record) 82*9c5db199SXin Li 83*9c5db199SXin Li 84*9c5db199SXin Liclass LogRecordSocketReceiver(six.moves.socketserver.ThreadingTCPServer): 85*9c5db199SXin Li """Simple TCP socket-based logging receiver. 86*9c5db199SXin Li """ 87*9c5db199SXin Li 88*9c5db199SXin Li allow_reuse_address = 1 89*9c5db199SXin Li 90*9c5db199SXin Li def __init__(self, host='localhost', port=None, 91*9c5db199SXin Li handler=LogRecordStreamHandler): 92*9c5db199SXin Li if not port: 93*9c5db199SXin Li port = utils.get_unused_port() 94*9c5db199SXin Li six.moves.socketserver.ThreadingTCPServer.__init__(self, (host, port), handler) 95*9c5db199SXin Li self.abort = 0 96*9c5db199SXin Li self.timeout = 1 97*9c5db199SXin Li self.logname = None 98*9c5db199SXin Li self.port = port 99*9c5db199SXin Li 100*9c5db199SXin Li 101*9c5db199SXin Li def serve_until_stopped(self): 102*9c5db199SXin Li """Run the socket receiver until aborted.""" 103*9c5db199SXin Li print('Log Record Socket Receiver is started.') 104*9c5db199SXin Li abort = 0 105*9c5db199SXin Li while not abort: 106*9c5db199SXin Li rd, wr, ex = select.select([self.socket.fileno()], [], [], 107*9c5db199SXin Li self.timeout) 108*9c5db199SXin Li if rd: 109*9c5db199SXin Li self.handle_request() 110*9c5db199SXin Li abort = self.abort 111*9c5db199SXin Li print('Log Record Socket Receiver is stopped.') 112*9c5db199SXin Li 113*9c5db199SXin Li 114*9c5db199SXin Liclass LogSocketServer: 115*9c5db199SXin Li """A wrapper class to start and stop a TCP server for logging.""" 116*9c5db199SXin Li 117*9c5db199SXin Li process = None 118*9c5db199SXin Li port = None 119*9c5db199SXin Li 120*9c5db199SXin Li @staticmethod 121*9c5db199SXin Li def start(**kwargs): 122*9c5db199SXin Li """Start Log Record Socket Receiver in a new process. 123*9c5db199SXin Li 124*9c5db199SXin Li @param kwargs: log configuration, e.g., format, filename. 125*9c5db199SXin Li 126*9c5db199SXin Li @raise Exception: if TCP server is already running. 127*9c5db199SXin Li """ 128*9c5db199SXin Li if LogSocketServer.process: 129*9c5db199SXin Li raise Exception('Log Record Socket Receiver is already running.') 130*9c5db199SXin Li server_started = multiprocessing.Value(ctypes.c_bool, False) 131*9c5db199SXin Li port = multiprocessing.Value(ctypes.c_int, 0) 132*9c5db199SXin Li LogSocketServer.process = multiprocessing.Process( 133*9c5db199SXin Li target=LogSocketServer._start_server, 134*9c5db199SXin Li args=(server_started, port), 135*9c5db199SXin Li kwargs=kwargs) 136*9c5db199SXin Li LogSocketServer.process.start() 137*9c5db199SXin Li while not server_started.value: 138*9c5db199SXin Li time.sleep(0.1) 139*9c5db199SXin Li LogSocketServer.port = port.value 140*9c5db199SXin Li print('Log Record Socket Server is started at port %d.' % port.value) 141*9c5db199SXin Li 142*9c5db199SXin Li 143*9c5db199SXin Li @staticmethod 144*9c5db199SXin Li def _start_server(server_started, port, **kwargs): 145*9c5db199SXin Li """Start the TCP server to receive log. 146*9c5db199SXin Li 147*9c5db199SXin Li @param server_started: True if socket log server is started. 148*9c5db199SXin Li @param port: Port used by socket log server. 149*9c5db199SXin Li @param kwargs: log configuration, e.g., format, filename. 150*9c5db199SXin Li """ 151*9c5db199SXin Li # Clear all existing log handlers. 152*9c5db199SXin Li logging.getLogger().handlers = [] 153*9c5db199SXin Li if not kwargs: 154*9c5db199SXin Li logging.basicConfig( 155*9c5db199SXin Li format='%(asctime)s - %(levelname)s - %(message)s') 156*9c5db199SXin Li else: 157*9c5db199SXin Li logging.basicConfig(**kwargs) 158*9c5db199SXin Li 159*9c5db199SXin Li tcp_server = LogRecordSocketReceiver() 160*9c5db199SXin Li print('Starting TCP server...') 161*9c5db199SXin Li server_started.value = True 162*9c5db199SXin Li port.value = tcp_server.port 163*9c5db199SXin Li tcp_server.serve_until_stopped() 164*9c5db199SXin Li 165*9c5db199SXin Li 166*9c5db199SXin Li @staticmethod 167*9c5db199SXin Li def stop(): 168*9c5db199SXin Li """Stop Log Record Socket Receiver. 169*9c5db199SXin Li """ 170*9c5db199SXin Li if LogSocketServer.process: 171*9c5db199SXin Li LogSocketServer.process.terminate() 172*9c5db199SXin Li LogSocketServer.process = None 173*9c5db199SXin Li LogSocketServer.port = None 174