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