You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2021/08/18 03:42:55 UTC

[apisix-python-plugin-runner] branch master updated: feat: add config and logger module (#19)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-python-plugin-runner.git


The following commit(s) were added to refs/heads/master by this push:
     new e2f32fd  feat: add config and logger module (#19)
e2f32fd is described below

commit e2f32fdcc85e8585305b88cc9e7e56de082ea465
Author: 帅进超 <sh...@gmail.com>
AuthorDate: Wed Aug 18 11:42:49 2021 +0800

    feat: add config and logger module (#19)
---
 Makefile                                           |   4 +
 apisix/{main.py => config.yaml}                    |  32 +----
 apisix/main.py                                     |  11 +-
 apisix/runner/plugin/core.py                       |  14 ++-
 apisix/runner/server/config.py                     | 130 +++++++++++++++++++++
 apisix/runner/server/handle.py                     |  35 +++---
 apisix/runner/server/logger.py                     |  89 ++++++++++++++
 apisix/runner/server/protocol.py                   |  18 +--
 apisix/runner/server/response.py                   |  56 ++++++---
 apisix/runner/server/server.py                     |  28 +++--
 docs/en/latest/getting-started.md                  |  13 ++-
 requirements.txt                                   |   1 +
 .../main.py => tests/runner/server/test_config.py  |  36 +++---
 tests/runner/server/test_handle.py                 |  18 +--
 .../main.py => tests/runner/server/test_logger.py  |  39 +++----
 tests/runner/server/test_protocol.py               |  12 +-
 tests/runner/server/test_response.py               |  24 ++--
 17 files changed, 403 insertions(+), 157 deletions(-)

diff --git a/Makefile b/Makefile
index b290d94..a3fd453 100644
--- a/Makefile
+++ b/Makefile
@@ -46,3 +46,7 @@ clean:
 	find . -name "*.pyc" -exec rm -r {} +
 
 
+.PHONY: dev
+dev:
+	export PYTHONPATH=$PYTHONPATH:$PWD
+	python3 apisix/main.py start
diff --git a/apisix/main.py b/apisix/config.yaml
similarity index 56%
copy from apisix/main.py
copy to apisix/config.yaml
index 3da621d..5e847ab 100644
--- a/apisix/main.py
+++ b/apisix/config.yaml
@@ -14,33 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import os
-import click
 
-from apisix.runner.server.server import Server as NewServer
+socket:
+  file: $env.APISIX_LISTEN_ADDRESS # Environment variable or absolute path
 
-RUNNER_VERSION = "0.1.0"
-RUNNER_SOCKET = os.getenv("APISIX_LISTEN_ADDRESS", "/tmp/runner.sock")
-
-
-@click.group()
-@click.version_option(version=RUNNER_VERSION)
-def runner() -> None:
-    pass
-
-
-@runner.command()
-@click.option('--debug/--no-debug', help='enable or disable debug, default disable.', default=False)
-def start(debug) -> None:
-    click.echo(debug)
-    click.echo(RUNNER_SOCKET)
-    server = NewServer(RUNNER_SOCKET)
-    server.receive()
-
-
-def main() -> None:
-    runner()
-
-
-if __name__ == '__main__':
-    main()
+logging:
+  level: warn # error warn info debug
diff --git a/apisix/main.py b/apisix/main.py
index 3da621d..6cee8cf 100644
--- a/apisix/main.py
+++ b/apisix/main.py
@@ -14,13 +14,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import os
 import click
 
 from apisix.runner.server.server import Server as NewServer
+from apisix.runner.server.config import Config as NewConfig
 
 RUNNER_VERSION = "0.1.0"
-RUNNER_SOCKET = os.getenv("APISIX_LISTEN_ADDRESS", "/tmp/runner.sock")
 
 
 @click.group()
@@ -30,11 +29,9 @@ def runner() -> None:
 
 
 @runner.command()
-@click.option('--debug/--no-debug', help='enable or disable debug, default disable.', default=False)
-def start(debug) -> None:
-    click.echo(debug)
-    click.echo(RUNNER_SOCKET)
-    server = NewServer(RUNNER_SOCKET)
+def start() -> None:
+    config = NewConfig()
+    server = NewServer(config)
     server.receive()
 
 
diff --git a/apisix/runner/plugin/core.py b/apisix/runner/plugin/core.py
index 4ebe848..b2b6bab 100644
--- a/apisix/runner/plugin/core.py
+++ b/apisix/runner/plugin/core.py
@@ -17,18 +17,22 @@
 import os
 import importlib
 from pkgutil import iter_modules
+from typing import Tuple
+from apisix.runner.server.response import RESP_STATUS_CODE_OK
+from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
+from apisix.runner.server.response import RESP_STATUS_CODE_SERVICE_UNAVAILABLE
 
 
-def filter(configs: dict, request, response) -> None:
+def execute(configs: dict, request, response) -> Tuple[int, str]:
     for name in configs:
         plugin = configs.get(name)
         if not plugin:
-            print("ERR: plugin `%s` undefined." % name)
-            continue
+            return RESP_STATUS_CODE_SERVICE_UNAVAILABLE, "plugin `%s` undefined" % name
         try:
             plugin.filter(request, response)
-        finally:
-            print("ERR: plugin `%s` filter execute failure" % name)
+        except AttributeError:
+            return RESP_STATUS_CODE_SERVICE_UNAVAILABLE, "plugin `%s` object has no attribute `filter`" % name
+    return RESP_STATUS_CODE_OK, RESP_STATUS_MESSAGE_OK
 
 
 def loading() -> dict:
diff --git a/apisix/runner/server/config.py b/apisix/runner/server/config.py
new file mode 100644
index 0000000..f3c09cf
--- /dev/null
+++ b/apisix/runner/server/config.py
@@ -0,0 +1,130 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+import yaml
+import logging
+
+
+class _ConfigSocket:
+
+    def __init__(self):
+        """
+        init socket config handler
+        """
+        self.file = "/tmp/runner.sock"
+
+    @property
+    def file(self):
+        """
+        get config file for socket
+        :return:
+        """
+        return self._file
+
+    @file.setter
+    def file(self, file: str) -> None:
+        """
+        set config file for socket
+        :param file:
+        :return:
+        """
+        self._file = file.replace("unix:", "")
+
+
+class _ConfigLogging:
+
+    def __init__(self):
+        self.level = "NOTSET"
+
+    @property
+    def level(self) -> int:
+        """
+        get config level for socket
+        :return:
+        """
+        return self._level
+
+    @level.setter
+    def level(self, level: str) -> None:
+        """
+        set config level for socket
+        :param level:
+        :return:
+        """
+        level = level.upper()
+        _name_to_level = {
+            'ERROR': logging.ERROR,
+            'WARN': logging.WARNING,
+            'INFO': logging.INFO,
+            'DEBUG': logging.DEBUG,
+            'NOTSET': logging.NOTSET,
+        }
+        self._level = _name_to_level.get(level, logging.NOTSET)
+
+
+class Config:
+
+    def __init__(self, config_name: str = "config.yaml"):
+        """
+        init config
+        :param config_name:
+            local config file name
+        """
+        self.socket = _ConfigSocket()
+        self.logging = _ConfigLogging()
+        self._loading_config(config_name)
+
+    @staticmethod
+    def _get_env_config(config: str):
+        """
+        get the configuration in the local environment variable
+        :param config:
+        :return:
+        """
+        if isinstance(config, str) and config.find("$env.") != -1:
+            env_name = config.replace("$env.", "")
+            return os.getenv(env_name)
+        return config
+
+    def _loading_config(self, config_name: str):
+        """
+        load local configuration file
+        :param config_name:
+        :return:
+        """
+        abs_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
+        cf_path = "%s/%s" % (abs_path, config_name)
+        if not os.path.exists(cf_path):
+            print("ERR: config file `%s` not exists" % cf_path)
+            exit(1)
+
+        # reading config file
+        fs = open(cf_path, encoding="UTF-8")
+        configs = yaml.load(fs, Loader=yaml.FullLoader)
+
+        # socket config
+        socket = configs.get("socket", {})
+        socket_file = self._get_env_config(socket.get("file"))
+        if socket_file:
+            self.socket.file = socket_file
+
+        # logging config
+        logger = configs.get("logging", {})
+        logger_level = self._get_env_config(logger.get("level"))
+        if logger_level:
+            self.logging.level = logger_level
diff --git a/apisix/runner/server/handle.py b/apisix/runner/server/handle.py
index 18fcb60..761c23d 100644
--- a/apisix/runner/server/handle.py
+++ b/apisix/runner/server/handle.py
@@ -15,15 +15,18 @@
 # limitations under the License.
 #
 
-from a6pluginproto.Err import Code as A6ErrCode
 import apisix.runner.plugin.core as RunnerPlugin
 import apisix.runner.plugin.cache as RunnerCache
 from apisix.runner.http.response import Response as NewHttpResponse
 from apisix.runner.http.response import RESP_MAX_DATA_SIZE
 from apisix.runner.http.request import Request as NewHttpRequest
 from apisix.runner.server.response import Response as NewServerResponse
-from apisix.runner.server.response import RUNNER_SUCCESS_CODE
-from apisix.runner.server.response import RUNNER_SUCCESS_MESSAGE
+from apisix.runner.server.response import RESP_STATUS_CODE_OK
+from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
+from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
+from apisix.runner.server.response import RESP_STATUS_MESSAGE_BAD_REQUEST
+from apisix.runner.server.response import RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
+from apisix.runner.server.response import RESP_STATUS_CODE_SERVICE_UNAVAILABLE
 from apisix.runner.http.protocol import RPC_PREPARE_CONF
 from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
 from apisix.runner.http.protocol import RPC_UNKNOWN
@@ -79,13 +82,14 @@ class Handle:
         # cache plugins config
         ok = RunnerCache.set_config_by_token(token, configs)
         if not ok:
-            return NewServerResponse(code=A6ErrCode.Code.SERVICE_UNAVAILABLE, message="cache token failure")
+            return NewServerResponse(code=RESP_STATUS_CODE_SERVICE_UNAVAILABLE,
+                                     message="token `%d` cache setting failed" % token)
         # init response
         resp = NewHttpResponse(RPC_PREPARE_CONF)
         resp.token = token
         response = resp.flatbuffers()
 
-        return NewServerResponse(code=RUNNER_SUCCESS_CODE, message=RUNNER_SUCCESS_MESSAGE, data=response.Output(),
+        return NewServerResponse(code=RESP_STATUS_CODE_OK, message=RESP_STATUS_MESSAGE_OK, data=response.Output(),
                                  ty=self.type)
 
     def _rpc_call(self) -> NewServerResponse:
@@ -96,22 +100,24 @@ class Handle:
         # get plugins
         configs = RunnerCache.get_config_by_token(token)
         if len(configs) == 0:
-            return NewServerResponse(code=A6ErrCode.Code.CONF_TOKEN_NOT_FOUND, message="cache token not found")
+            return NewServerResponse(code=RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND,
+                                     message="token `%d` cache acquisition failed" % token)
         # init response
         resp = NewHttpResponse(RPC_HTTP_REQ_CALL)
         # execute plugins
-        RunnerPlugin.filter(configs, req, resp)
+        (code, message) = RunnerPlugin.execute(configs, req, resp)
 
         response = resp.flatbuffers()
-        return NewServerResponse(code=RUNNER_SUCCESS_CODE, message=RUNNER_SUCCESS_MESSAGE, data=response.Output(),
+        return NewServerResponse(code=code, message=message, data=response.Output(),
                                  ty=self.type)
 
     @staticmethod
-    def _rpc_unknown(err_code: int = 0) -> NewServerResponse:
+    def _rpc_unknown(err_code: int = RESP_STATUS_CODE_BAD_REQUEST,
+                     err_message: str = RESP_STATUS_MESSAGE_BAD_REQUEST) -> NewServerResponse:
         resp = NewHttpResponse(RPC_UNKNOWN)
         resp.error_code = err_code
         response = resp.flatbuffers()
-        return NewServerResponse(code=RUNNER_SUCCESS_CODE, message="OK", data=response.Output(),
+        return NewServerResponse(code=err_code, message=err_message, data=response.Output(),
                                  ty=RPC_UNKNOWN)
 
     def dispatch(self) -> NewServerResponse:
@@ -127,11 +133,10 @@ class Handle:
             return self._rpc_unknown()
 
         size = len(resp.data)
-        if (size > RESP_MAX_DATA_SIZE or size <= 0) and resp.code == 200:
-            resp = NewServerResponse(A6ErrCode.Code.SERVICE_UNAVAILABLE,
-                                     "the max length of data is %d but got %d" % (
+        if (size > RESP_MAX_DATA_SIZE or size <= 0) and resp.code == RESP_STATUS_CODE_OK:
+            resp = NewServerResponse(RESP_STATUS_CODE_SERVICE_UNAVAILABLE,
+                                     "The maximum length of the data is %d, the minimum is 1, but got %d" % (
                                          RESP_MAX_DATA_SIZE, size))
         if resp.code != 200:
-            print("ERR: %s" % resp.message)
-            resp = self._rpc_unknown(resp.code)
+            resp = self._rpc_unknown(resp.code, resp.message)
         return resp
diff --git a/apisix/runner/server/logger.py b/apisix/runner/server/logger.py
new file mode 100644
index 0000000..472392b
--- /dev/null
+++ b/apisix/runner/server/logger.py
@@ -0,0 +1,89 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import sys
+import logging
+
+
+class Logger:
+    def __init__(self, level: int = logging.NOTSET):
+        """
+        init server logger handler
+        :param level:
+            logger level
+        """
+        self.logger = logging
+        self._init(level)
+
+    def set_level(self, level: int):
+        """
+        set level and reset logger
+        :param level:
+        :return:
+        """
+        self._init(level)
+
+    def _init(self, level: int):
+        """
+        init logger
+        :param level:
+        :return:
+        """
+        self.logger = logging.getLogger()
+        self.logger.setLevel(level)
+
+        handler = logging.StreamHandler(sys.stdout)
+        handler.setLevel(level)
+        formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
+        handler.setFormatter(formatter)
+        self.logger.addHandler(handler)
+
+    def info(self, message: str, *objs):
+        """
+        info level logger output
+        :param message:
+        :param objs:
+        :return:
+        """
+        self.logger.info(message.format(*objs))
+
+    def error(self, message: str, *objs):
+        """
+        error level logger output
+        :param message:
+        :param objs:
+        :return:
+        """
+        self.logger.error(message.format(*objs))
+
+    def debug(self, message: str, *objs):
+        """
+        debug level logger output
+        :param message:
+        :param objs:
+        :return:
+        """
+        self.logger.debug(message.format(*objs))
+
+    def warn(self, message: str, *objs):
+        """
+        warning level logger output
+        :param message:
+        :param objs:
+        :return:
+        """
+        self.logger.warning(message.format(*objs))
diff --git a/apisix/runner/server/protocol.py b/apisix/runner/server/protocol.py
index b2e3fc8..3343f11 100644
--- a/apisix/runner/server/protocol.py
+++ b/apisix/runner/server/protocol.py
@@ -15,9 +15,9 @@
 # limitations under the License.
 #
 from apisix.runner.server.response import Response as NewServerResponse
-from apisix.runner.server.response import RUNNER_ERROR_CODE
-from apisix.runner.server.response import RUNNER_SUCCESS_CODE
-from apisix.runner.server.response import RUNNER_SUCCESS_MESSAGE
+from apisix.runner.server.response import RESP_STATUS_CODE_OK
+from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
+from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
 
 
 class Protocol:
@@ -57,7 +57,7 @@ class Protocol:
         :return:
         """
         if len(self.__buffer) == 0:
-            return NewServerResponse(RUNNER_ERROR_CODE, "ERR: send buffer is empty")
+            return NewServerResponse(RESP_STATUS_CODE_BAD_REQUEST, "send buffer is empty")
         response_len = len(self.__buffer)
         response_header = response_len.to_bytes(4, byteorder="big")
         response_header = bytearray(response_header)
@@ -65,7 +65,7 @@ class Protocol:
         response_header = bytes(response_header)
         self.__buffer = response_header + self.__buffer
         self.__length = len(self.__buffer)
-        return NewServerResponse(code=RUNNER_SUCCESS_CODE, message=RUNNER_SUCCESS_MESSAGE)
+        return NewServerResponse(code=RESP_STATUS_CODE_OK, message=RESP_STATUS_MESSAGE_OK)
 
     def decode(self) -> NewServerResponse:
         """
@@ -73,14 +73,14 @@ class Protocol:
         :return:
         """
         if len(self.__buffer) == 0:
-            return NewServerResponse(RUNNER_ERROR_CODE, "ERR: recv buffer is empty")
+            return NewServerResponse(RESP_STATUS_CODE_BAD_REQUEST, "recv buffer is empty")
         length = len(self.__buffer)
         if length != 4:
-            return NewServerResponse(RUNNER_ERROR_CODE,
-                                     "ERR: recv protocol type length is 4, got %d" % length)
+            return NewServerResponse(RESP_STATUS_CODE_BAD_REQUEST,
+                                     "recv protocol type length is 4, got %d" % length)
 
         buf = bytearray(self.__buffer)
         self.__type = buf[0]
         buf[0] = 0
         self.__length = int.from_bytes(buf, byteorder="big")
-        return NewServerResponse(code=RUNNER_SUCCESS_CODE, message=RUNNER_SUCCESS_MESSAGE)
+        return NewServerResponse(code=RESP_STATUS_CODE_OK, message=RESP_STATUS_MESSAGE_OK)
diff --git a/apisix/runner/server/response.py b/apisix/runner/server/response.py
index f5a2a8c..2835910 100644
--- a/apisix/runner/server/response.py
+++ b/apisix/runner/server/response.py
@@ -17,43 +17,71 @@
 
 from a6pluginproto.Err import Code as A6ErrCode
 
-RUNNER_SUCCESS_CODE = 200
-RUNNER_SUCCESS_MESSAGE = "OK"
-RUNNER_ERROR_CODE = 500
-RUNNER_ERROR_MESSAGE = "ERR"
-
-errorCodes = [
-    A6ErrCode.Code.CONF_TOKEN_NOT_FOUND,
-    A6ErrCode.Code.BAD_REQUEST,
-    A6ErrCode.Code.SERVICE_UNAVAILABLE,
-]
+RESP_STATUS_CODE_OK = 200
+RESP_STATUS_MESSAGE_OK = "OK"
+RESP_STATUS_CODE_BAD_REQUEST = A6ErrCode.Code.BAD_REQUEST
+RESP_STATUS_MESSAGE_BAD_REQUEST = "Bad Request"
+RESP_STATUS_CODE_SERVICE_UNAVAILABLE = A6ErrCode.Code.BAD_REQUEST
+RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND = A6ErrCode.Code.SERVICE_UNAVAILABLE
 
 
 class Response:
     def __init__(self, code: int = 0, message: str = '', data: bytes = b'', ty: int = 0):
+        """
+        init response handler
+        :param code:
+            response code
+        :param message:
+            response message
+        :param data:
+            response data
+        :param ty:
+            response type
+        """
         self.__code = code
         self.__message = message
         self.__type = ty
         self.__data = data
 
     def __eq__(self, response) -> bool:
-        return self.code == response.code and \
-               self.message == response.message and \
-               self.data == response.data and \
-               self.type == response.type
+        """
+        compare response handler
+        :param response:
+            response handler
+        :return:
+        """
+        return (
+                self.code == response.code and self.message == response.message and self.data == response.data and
+                self.type == response.type)
 
     @property
     def code(self) -> int:
+        """
+        get code by response handler
+        :return:
+        """
         return self.__code
 
     @property
     def message(self) -> str:
+        """
+        get message by response handler
+        :return:
+        """
         return self.__message
 
     @property
     def data(self) -> bytes:
+        """
+        get data by response handler
+        :return:
+        """
         return self.__data
 
     @property
     def type(self) -> int:
+        """
+        get type by response handler
+        :return:
+        """
         return self.__type
diff --git a/apisix/runner/server/server.py b/apisix/runner/server/server.py
index 2ad741e..53ca38c 100644
--- a/apisix/runner/server/server.py
+++ b/apisix/runner/server/server.py
@@ -14,30 +14,43 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
 import os
 import socket
+
 from threading import Thread as NewThread
 from apisix.runner.server.handle import Handle as NewServerHandle
 from apisix.runner.server.protocol import Protocol as NewServerProtocol
+from apisix.runner.server.config import Config as NewServerConfig
+from apisix.runner.server.logger import Logger as NewServerLogger
+from apisix.runner.server.response import RESP_STATUS_CODE_OK
+
+logger = NewServerLogger()
 
 
-def _threaded(conn: socket, debug: bool):
+def _threaded(conn: socket):
     while True:
         buffer = conn.recv(4)
         protocol = NewServerProtocol(buffer, 0)
         err = protocol.decode()
-        if err.code != 200:
-            print(err.message)
+        if err.code != RESP_STATUS_CODE_OK:
+            logger.error(err.message)
             break
 
+        logger.info("request type:{}, len:{}", protocol.type, protocol.length)
+
         buffer = conn.recv(protocol.length)
         handler = NewServerHandle(protocol.type, buffer)
         response = handler.dispatch()
+        if response.code != RESP_STATUS_CODE_OK:
+            logger.error(response.message)
 
         protocol = NewServerProtocol(response.data, response.type)
         protocol.encode()
         response = protocol.buffer
 
+        logger.info("response type:{}, len:{}", protocol.type, protocol.length)
+
         err = conn.sendall(response)
         if err:
             print(err)
@@ -47,21 +60,22 @@ def _threaded(conn: socket, debug: bool):
 
 
 class Server:
-    def __init__(self, fd: str, debug: bool = False):
-        self.fd = fd.replace("unix:", "")
-        self.debug = debug
+    def __init__(self, config: NewServerConfig):
+        self.fd = config.socket.file
         if os.path.exists(self.fd):
             os.remove(self.fd)
         self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         self.sock.bind(self.fd)
         self.sock.listen(1024)
+
+        logger.set_level(config.logging.level)
         print("listening on unix:%s" % self.fd)
 
     def receive(self):
         while True:
             conn, address = self.sock.accept()
 
-            NewThread(target=_threaded, args=(conn, self.debug)).start()
+            NewThread(target=_threaded, args=(conn,)).start()
 
     def __del__(self):
         self.sock.close()
diff --git a/docs/en/latest/getting-started.md b/docs/en/latest/getting-started.md
index 1550e46..1fdaf95 100644
--- a/docs/en/latest/getting-started.md
+++ b/docs/en/latest/getting-started.md
@@ -73,7 +73,18 @@ apisix:
       key: edd1c9f034335f136f87ad84b625c8f1
       role: admin
 ext-plugin:
-  cmd: [ "python3", "/path/to/apache/apisix-python-plugin-runner/apisix/main.py", "start" ]
+  cmd: [ "python3", "/path/to/apisix-python-plugin-runner/apisix/main.py", "start" ]
+```
+
+### Log level and socket configuration (Optional)
+
+```bash
+$ vim /path/to/apisix-python-plugin-runner/apisix/config.yaml
+socket:
+  file: $env.APISIX_LISTEN_ADDRESS # Environment variable or absolute path
+
+logging:
+  level: debug # error warn info debug
 ```
 
 ### Start or Restart APISIX
diff --git a/requirements.txt b/requirements.txt
index 0cd85b8..1b4de80 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 a6pluginprotos==0.1.0
 click==8.0.1
 minicache==0.0.1
+PyYAML==5.4.1
diff --git a/apisix/main.py b/tests/runner/server/test_config.py
similarity index 56%
copy from apisix/main.py
copy to tests/runner/server/test_config.py
index 3da621d..56df878 100644
--- a/apisix/main.py
+++ b/tests/runner/server/test_config.py
@@ -14,33 +14,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import os
-import click
 
-from apisix.runner.server.server import Server as NewServer
+import logging
+from apisix.runner.server.config import Config as NewServerConfig
 
-RUNNER_VERSION = "0.1.0"
-RUNNER_SOCKET = os.getenv("APISIX_LISTEN_ADDRESS", "/tmp/runner.sock")
 
+def test_config():
+    config = NewServerConfig()
 
-@click.group()
-@click.version_option(version=RUNNER_VERSION)
-def runner() -> None:
-    pass
+    config.logging.level = "INFO"
+    assert config.logging.level == logging.INFO
 
+    config.logging.level = "ERROR"
+    assert config.logging.level == logging.ERROR
 
-@runner.command()
-@click.option('--debug/--no-debug', help='enable or disable debug, default disable.', default=False)
-def start(debug) -> None:
-    click.echo(debug)
-    click.echo(RUNNER_SOCKET)
-    server = NewServer(RUNNER_SOCKET)
-    server.receive()
+    config.logging.level = "WARN"
+    assert config.logging.level == logging.WARNING
 
+    config.logging.level = "NOTSET"
+    assert config.logging.level == logging.NOTSET
 
-def main() -> None:
-    runner()
-
-
-if __name__ == '__main__':
-    main()
+    config.socket.file = "/test/runner.sock"
+    assert config.socket.file == "/test/runner.sock"
diff --git a/tests/runner/server/test_handle.py b/tests/runner/server/test_handle.py
index 56ede96..2904c21 100644
--- a/tests/runner/server/test_handle.py
+++ b/tests/runner/server/test_handle.py
@@ -20,8 +20,11 @@ from apisix.runner.http.protocol import RPC_PREPARE_CONF
 from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
 from apisix.runner.http.protocol import RPC_UNKNOWN
 from apisix.runner.http.protocol import new_builder
-from apisix.runner.server.response import RUNNER_SUCCESS_CODE
-from apisix.runner.server.response import RUNNER_SUCCESS_MESSAGE
+from apisix.runner.server.response import RESP_STATUS_CODE_OK
+from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
+from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
+from apisix.runner.server.response import RESP_STATUS_MESSAGE_BAD_REQUEST
+from apisix.runner.server.response import RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
 from a6pluginproto.HTTPReqCall import Req as A6HTTPReqCallReq
 from a6pluginproto.PrepareConf import Req as A6PrepareConfReq
 from a6pluginproto.PrepareConf import Resp as A6PrepareConfResp
@@ -71,8 +74,8 @@ def test_dispatch_config():
     handle = NewServerHandle(ty=RPC_PREPARE_CONF, buf=buf)
     response = handle.dispatch()
     resp = A6PrepareConfResp.Resp.GetRootAs(response.data)
-    assert response.code == RUNNER_SUCCESS_CODE
-    assert response.message == RUNNER_SUCCESS_MESSAGE
+    assert response.code == RESP_STATUS_CODE_OK
+    assert response.message == RESP_STATUS_MESSAGE_OK
     assert response.type == RPC_PREPARE_CONF
     assert resp.ConfToken() != 0
 
@@ -118,14 +121,13 @@ def test_dispatch_call():
 
     handle = NewServerHandle(ty=RPC_HTTP_REQ_CALL, buf=buf)
     response = handle.dispatch()
-    assert response.code == RUNNER_SUCCESS_CODE
-    assert response.message == RUNNER_SUCCESS_MESSAGE
+    assert response.code == RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
     assert response.type == RPC_UNKNOWN
 
 
 def test_dispatch_unknown():
     handle = NewServerHandle(ty=RPC_UNKNOWN)
     response = handle.dispatch()
-    assert response.code == RUNNER_SUCCESS_CODE
-    assert response.message == RUNNER_SUCCESS_MESSAGE
+    assert response.code == RESP_STATUS_CODE_BAD_REQUEST
+    assert response.message == RESP_STATUS_MESSAGE_BAD_REQUEST
     assert response.type == RPC_UNKNOWN
diff --git a/apisix/main.py b/tests/runner/server/test_logger.py
similarity index 56%
copy from apisix/main.py
copy to tests/runner/server/test_logger.py
index 3da621d..941c96f 100644
--- a/apisix/main.py
+++ b/tests/runner/server/test_logger.py
@@ -14,33 +14,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import os
-import click
 
-from apisix.runner.server.server import Server as NewServer
+import logging
 
-RUNNER_VERSION = "0.1.0"
-RUNNER_SOCKET = os.getenv("APISIX_LISTEN_ADDRESS", "/tmp/runner.sock")
+from apisix.runner.server.logger import Logger as NewServerLogger
 
 
-@click.group()
-@click.version_option(version=RUNNER_VERSION)
-def runner() -> None:
-    pass
-
-
-@runner.command()
-@click.option('--debug/--no-debug', help='enable or disable debug, default disable.', default=False)
-def start(debug) -> None:
-    click.echo(debug)
-    click.echo(RUNNER_SOCKET)
-    server = NewServer(RUNNER_SOCKET)
-    server.receive()
-
-
-def main() -> None:
-    runner()
-
-
-if __name__ == '__main__':
-    main()
+def test_logger(capsys):
+    logger = NewServerLogger(logging.DEBUG)
+    logger.error("test error log")
+    logger.warn("test warn log")
+    logger.info("test info log")
+    logger.debug("test debug log")
+    captured = capsys.readouterr()
+    assert captured.out.find("test error log") != -1
+    assert captured.out.find("test warn log") != -1
+    assert captured.out.find("test info log") != -1
+    assert captured.out.find("test debug log") != -1
diff --git a/tests/runner/server/test_protocol.py b/tests/runner/server/test_protocol.py
index 019f653..a38c7b7 100644
--- a/tests/runner/server/test_protocol.py
+++ b/tests/runner/server/test_protocol.py
@@ -17,8 +17,8 @@
 
 from apisix.runner.server.protocol import Protocol as NewServerProtocol
 from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from apisix.runner.server.response import RUNNER_SUCCESS_CODE
-from apisix.runner.server.response import RUNNER_SUCCESS_MESSAGE
+from apisix.runner.server.response import RESP_STATUS_CODE_OK
+from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
 
 
 def test_protocol_encode():
@@ -30,8 +30,8 @@ def test_protocol_encode():
     buf_arr[0] = RPC_PREPARE_CONF
     buf_data = bytes(buf_arr) + buf_str
     buf_len = len(buf_data)
-    assert err.code == RUNNER_SUCCESS_CODE
-    assert err.message == RUNNER_SUCCESS_MESSAGE
+    assert err.code == RESP_STATUS_CODE_OK
+    assert err.message == RESP_STATUS_MESSAGE_OK
     assert protocol.type == RPC_PREPARE_CONF
     assert protocol.buffer == buf_data
     assert protocol.length == buf_len
@@ -45,7 +45,7 @@ def test_protocol_decode():
     buf_data = bytes(buf_arr)
     protocol = NewServerProtocol(buffer=buf_data)
     err = protocol.decode()
-    assert err.code == RUNNER_SUCCESS_CODE
-    assert err.message == RUNNER_SUCCESS_MESSAGE
+    assert err.code == RESP_STATUS_CODE_OK
+    assert err.message == RESP_STATUS_MESSAGE_OK
     assert protocol.type == RPC_PREPARE_CONF
     assert protocol.length == buf_len
diff --git a/tests/runner/server/test_response.py b/tests/runner/server/test_response.py
index 1dd8ee1..8fe6652 100644
--- a/tests/runner/server/test_response.py
+++ b/tests/runner/server/test_response.py
@@ -16,18 +16,24 @@
 #
 
 from apisix.runner.server.response import Response as NewServerResponse
-from apisix.runner.server.response import RUNNER_ERROR_CODE
-from apisix.runner.server.response import RUNNER_SUCCESS_CODE
+from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
+from apisix.runner.server.response import RESP_STATUS_CODE_SERVICE_UNAVAILABLE
+from apisix.runner.server.response import RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
+from apisix.runner.server.response import RESP_STATUS_CODE_OK
 from apisix.runner.http.protocol import RPC_PREPARE_CONF
 from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
 from apisix.runner.http.protocol import RPC_UNKNOWN
 
 
 def test_response_code():
-    response = NewServerResponse(code=RUNNER_SUCCESS_CODE)
-    assert response.code == RUNNER_SUCCESS_CODE
-    error = NewServerResponse(code=RUNNER_ERROR_CODE)
-    assert error.code == RUNNER_ERROR_CODE
+    resp = NewServerResponse(code=RESP_STATUS_CODE_OK)
+    assert resp.code == RESP_STATUS_CODE_OK
+    resp = NewServerResponse(code=RESP_STATUS_CODE_BAD_REQUEST)
+    assert resp.code == RESP_STATUS_CODE_BAD_REQUEST
+    resp = NewServerResponse(code=RESP_STATUS_CODE_SERVICE_UNAVAILABLE)
+    assert resp.code == RESP_STATUS_CODE_SERVICE_UNAVAILABLE
+    resp = NewServerResponse(code=RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND)
+    assert resp.code == RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
 
 
 def test_response_message():
@@ -50,11 +56,11 @@ def test_response_type():
 
 
 def test_response_eq():
-    resp1 = NewServerResponse(code=RUNNER_SUCCESS_CODE, message="Hello Python Runner",
+    resp1 = NewServerResponse(code=RESP_STATUS_CODE_OK, message="Hello Python Runner",
                               data="Hello Python Runner".encode(), ty=RPC_PREPARE_CONF)
-    resp2 = NewServerResponse(code=RUNNER_ERROR_CODE, message="Hello Python Runner",
+    resp2 = NewServerResponse(code=RESP_STATUS_CODE_BAD_REQUEST, message="Hello Python Runner",
                               data="Hello Python Runner".encode(), ty=RPC_PREPARE_CONF)
-    resp3 = NewServerResponse(code=RUNNER_SUCCESS_CODE, message="Hello Python Runner",
+    resp3 = NewServerResponse(code=RESP_STATUS_CODE_OK, message="Hello Python Runner",
                               data="Hello Python Runner".encode(), ty=RPC_PREPARE_CONF)
     assert resp1 != resp2
     assert resp1 == resp3