[autotest] RPC logging server

frontend.afe.rpc_handler is not logging RPC logs correctly
due to multi apache processes.
We should have a daemon that receives log records from the
apache processes and writes them in a chronological order.

BUG=chromium:490897
TEST=Run dummy suite on local machine.
DEPLOY=apache

Change-Id: If53667720922d0db248225d17b9e5a31d350f063
Reviewed-on: https://chromium-review.googlesource.com/274827
Reviewed-by: Dan Shi <dshi@chromium.org>
Reviewed-by: Fang Deng <fdeng@chromium.org>
Tested-by: Mungyung Ryu <mkryu@google.com>
Commit-Queue: Mungyung Ryu <mkryu@google.com>
diff --git a/frontend/afe/rpc_handler.py b/frontend/afe/rpc_handler.py
index 52c0adf..a197239 100644
--- a/frontend/afe/rpc_handler.py
+++ b/frontend/afe/rpc_handler.py
@@ -13,9 +13,10 @@
 
 LOGGING_REGEXPS = [r'.*add_.*',
                    r'delete_.*',
-                   r'.*_remove_.*',
+                   r'.*remove_.*',
                    r'modify_.*',
-                   r'create.*']
+                   r'create.*',
+                   r'set_.*']
 FULL_REGEXP = '(' + '|'.join(LOGGING_REGEXPS) + ')'
 COMPILED_REGEXP = re.compile(FULL_REGEXP)
 
diff --git a/frontend/afe/rpcserver_logging.py b/frontend/afe/rpcserver_logging.py
index a8523fb..f181672 100644
--- a/frontend/afe/rpcserver_logging.py
+++ b/frontend/afe/rpcserver_logging.py
@@ -1,29 +1,23 @@
 import logging, logging.handlers, time, os
 import common
 from autotest_lib.client.common_lib import global_config
+from autotest_lib.site_utils import rpc_logserver
 
 
 config = global_config.global_config
 LOGGING_ENABLED = config.get_config_value('SERVER', 'rpc_logging', type=bool)
 
-MEGABYTE = 1024 * 1024
-
 rpc_logger = None
 
-def configure_logging():
-    MAX_LOG_SIZE = config.get_config_value('SERVER', 'rpc_max_log_size_mb',
-                                           type=int)
-    NUMBER_OF_OLD_LOGS = config.get_config_value('SERVER', 'rpc_num_old_logs',
-                                                 type=int)
-    log_path = config.get_config_value('SERVER', 'rpc_log_path')
 
-    formatter = logging.Formatter(
-        fmt='[%(asctime)s %(levelname)-5.5s] %(message)s',
-        datefmt='%m/%d %H:%M:%S')
-    handler = logging.handlers.RotatingFileHandler(log_path,
-                                                 maxBytes=MAX_LOG_SIZE*MEGABYTE,
-                                                 backupCount=NUMBER_OF_OLD_LOGS)
-    handler.setFormatter(formatter)
+def configure_logging():
+    logserver_enabled = config.get_config_value(
+            'SERVER', 'rpc_logserver', type=bool)
+    if logserver_enabled:
+        handler = logging.handlers.SocketHandler(
+                'localhost', rpc_logserver.DEFAULT_PORT)
+    else:
+        handler = rpc_logserver.get_logging_handler()
 
     global rpc_logger
     rpc_logger = logging.getLogger('rpc_logger')
diff --git a/global_config.ini b/global_config.ini
index 637ef3b..f0702eb 100644
--- a/global_config.ini
+++ b/global_config.ini
@@ -104,6 +104,8 @@
 # Number of old logs to keep around
 rpc_num_old_logs: 5
 rpc_max_log_size_mb: 20
+# Transfer RPC logs to a RPC logging server
+rpc_logserver: False
 # Minimum amount of disk space required for AutoTest in GB
 gb_diskspace_required: 1.0
 # Minmum number of i-nodes for stateful, in 1000 i-node units.
diff --git a/site_utils/rpc_logserver.py b/site_utils/rpc_logserver.py
new file mode 100755
index 0000000..dd74457
--- /dev/null
+++ b/site_utils/rpc_logserver.py
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+import argparse
+import ctypes
+import logging
+import logging.handlers
+import multiprocessing
+import signal
+import sys
+import time
+
+import common
+from autotest_lib.client.common_lib.global_config import global_config as config
+from autotest_lib.site_utils import log_socket_server
+
+
+DEFAULT_PORT = 9080
+LOGGING_FORMAT = '%(asctime)s.%(msecs)03d %(levelname)-5.5s| %(message)s'
+MEGABYTE = 1024 * 1024
+
+
+class LogServerAlreadyRunningError(Exception):
+    pass
+
+
+class LogServer(object):
+    """A wrapper class to start and stop a TCP server for logging."""
+
+    process = None
+
+    @staticmethod
+    def start(port, log_handler):
+        """Start Log Record Socket Receiver in a new process.
+
+        @param port: Port to listen on.
+        @param log_handler: Logging handler.
+
+        @raise Exception: if TCP server is already running.
+        """
+        if LogServer.process:
+            raise LogServerAlreadyRunningError('LogServer is already running.')
+        server_started = multiprocessing.Value(ctypes.c_bool, False)
+        LogServer.process = multiprocessing.Process(
+                target=LogServer._run,
+                args=(server_started, port, log_handler))
+        LogServer.process.start()
+        while not server_started.value:
+            time.sleep(0.1)
+        print 'LogServer is started at port %d.' % port
+
+
+    @staticmethod
+    def _run(server_started, port, log_handler):
+        """Run LogRecordSocketReceiver to receive log.
+
+        @param server_started: True if socket log server is started.
+        @param port: Port used by socket log server.
+        @param log_handler: Logging handler.
+        """
+        # Clear all existing log handlers.
+        logging.getLogger().handlers = []
+        logging.getLogger().addHandler(log_handler)
+
+        tcp_server = log_socket_server.LogRecordSocketReceiver(
+                port=port)
+        print('Starting LogServer...')
+        server_started.value = True
+        tcp_server.serve_until_stopped()
+
+
+    @staticmethod
+    def stop():
+        """Stop LogServer."""
+        if LogServer.process:
+            LogServer.process.terminate()
+            LogServer.process = None
+
+
+def signal_handler(signal, frame):
+    """Handler for signal SIGINT.
+
+    @param signal: SIGINT
+    @param frame: the current stack frame
+    """
+    LogServer.stop()
+    sys.exit(0)
+
+
+def get_logging_handler():
+    """Return a logging handler.
+
+    Configure a RPC logging handler based on global_config and return
+    the handler.
+    """
+    max_log_size = config.get_config_value('SERVER', 'rpc_max_log_size_mb',
+                                           type=int)
+    number_of_old_logs = config.get_config_value('SERVER', 'rpc_num_old_logs',
+                                                 type=int)
+    log_path = config.get_config_value('SERVER', 'rpc_log_path')
+
+    formatter = logging.Formatter(
+            fmt=LOGGING_FORMAT, datefmt='%m/%d %H:%M:%S')
+    handler = logging.handlers.RotatingFileHandler(
+            log_path,
+            maxBytes=max_log_size*MEGABYTE,
+            backupCount=number_of_old_logs)
+    handler.setFormatter(formatter)
+    return handler
+
+
+def main():
+    parser = argparse.ArgumentParser(
+            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument('-p', type=int, dest='port',
+                        help=('Listening port number'), default=DEFAULT_PORT)
+    options = parser.parse_args()
+
+    signal.signal(signal.SIGINT, signal_handler)
+
+    LogServer.start(options.port, get_logging_handler())
+
+
+if __name__ == '__main__':
+    sys.exit(main())