You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. # Copyright 2019 Huawei Technologies Co., Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # ============================================================================
  15. """Web service entrance."""
  16. import os
  17. import stat
  18. import re
  19. import subprocess
  20. import time
  21. import shlex
  22. from gunicorn.glogging import Logger
  23. from mindinsight.backend.config import WEB_CONFIG_DIR
  24. from mindinsight.conf import settings
  25. from mindinsight.utils.log import setup_logger
  26. MINDBOARD_APP_MODULE = "mindinsight.backend.application:APP"
  27. GUNICORN_LOGGER = "mindinsight.backend.run.GunicornLogger"
  28. _MIN_PORT = 1
  29. _MAX_PORT = 65535
  30. def _get_file_size(file_path):
  31. """
  32. Get the file size.
  33. Args:
  34. file_path (str): The file path.
  35. Returns:
  36. int, the file size. If file is not existed, then return 0.
  37. """
  38. try:
  39. file_size = os.path.getsize(file_path)
  40. except FileNotFoundError:
  41. file_size = 0
  42. return file_size
  43. def _is_match_one(sub_string_list, src_string):
  44. """
  45. Whether the sub-string in the list can match the source string.
  46. Args:
  47. sub_string_list (list): The sub-string list.
  48. src_string (str): The source string.
  49. Returns:
  50. bool, if matched return True, else return False.
  51. """
  52. for match_info in sub_string_list:
  53. if match_info in src_string:
  54. return True
  55. return False
  56. def _check_stat_from_log(log_info):
  57. """
  58. Determine the service startup status based on the log information.
  59. Args:
  60. log_info (str): The output log of service startup.
  61. Returns:
  62. str, the state value that is one of the follows: "unknown", "failed" and "success".
  63. """
  64. server_state = "unknown"
  65. match_success_info = "Listening at: http://%s:%d" % \
  66. (settings.HOST, int(settings.PORT))
  67. common_failed_info_list = [
  68. "[ERROR] Retrying in 1 second",
  69. "[INFO] Reason: App failed to load",
  70. "[ERROR] Exception in worker process"
  71. ]
  72. re_pattern = "\\[ERROR\\].+%s.+%d" % \
  73. (settings.HOST, int(settings.PORT))
  74. # matched failed output log by fuzzy match
  75. if re.search(re_pattern, log_info) or \
  76. _is_match_one(common_failed_info_list, log_info):
  77. server_state = "failed"
  78. if match_success_info in log_info:
  79. server_state = "success"
  80. return server_state
  81. def _get_error_log_path():
  82. """
  83. Get gunicorn error log path.
  84. Returns:
  85. str, the path of error log.
  86. """
  87. path = os.path.join(settings.WORKSPACE, 'log/gunicorn/error.log')
  88. errorlog_abspath = os.path.realpath(path)
  89. return errorlog_abspath
  90. def _get_access_log_path():
  91. """Get gunicorn access log path."""
  92. access_log_path = os.path.join(settings.WORKSPACE, 'log/gunicorn/access.log')
  93. access_log_path = os.path.realpath(access_log_path)
  94. return access_log_path
  95. def _check_state_from_log(log_abspath, start_pos=0):
  96. """
  97. Check the service startup status based on the log file.
  98. Args:
  99. log_abspath (str): Absolute path of the log file.
  100. start_pos (int): Offset position of the log file.
  101. Returns:
  102. dict, a dict with "state" and "prompt_message" key.
  103. The value of the "state" key is as follows:"unknown", "failed" and "success".
  104. The value of the "prompt_message" key is a list of prompt messages.
  105. """
  106. server_is_start = False
  107. state_result = {"state": "unknown", "prompt_message": []}
  108. prompt_messages = []
  109. match_start_log = "Starting gunicorn"
  110. with open(log_abspath) as f_log:
  111. f_log.seek(start_pos)
  112. for line in f_log.readlines():
  113. if match_start_log in line:
  114. if server_is_start:
  115. break
  116. server_is_start = True
  117. continue
  118. if server_is_start:
  119. log_result = _check_stat_from_log(line)
  120. # ignore "unknown" result
  121. if log_result != "unknown":
  122. state_result["state"] = log_result
  123. if log_result == "failed":
  124. prompt_messages.append(line.strip())
  125. prompt_messages.append(
  126. "more failed details in log: %s" % log_abspath)
  127. break
  128. state_result["prompt_message"].append(
  129. "service start state: %s" % state_result["state"])
  130. for prompt_message in prompt_messages:
  131. state_result["prompt_message"].append(prompt_message)
  132. return state_result
  133. def _check_server_start_stat(log_abspath, start_pos=None):
  134. """
  135. Checking the Server Startup Status.
  136. Args:
  137. log_abspath (str): The log file path.
  138. start_pos (int): The log file start position.
  139. Returns:
  140. dict, an dict object that contains the state and prompt_message fields.
  141. The state values are as follows: "unknown", "failed" and "success".
  142. """
  143. state_result = {"state": "unknown", "prompt_message": []}
  144. # return unknown when not config gunicorn error log file
  145. if not log_abspath:
  146. return state_result
  147. log_pos = _get_file_size(log_abspath) if start_pos is None else start_pos
  148. try_cnt = 0
  149. try_cnt_max = 2
  150. while try_cnt < try_cnt_max:
  151. try_cnt += 1
  152. time.sleep(1)
  153. if _get_file_size(log_abspath) > log_pos:
  154. state_result.update(_check_state_from_log(log_abspath, log_pos))
  155. break
  156. return state_result
  157. class GunicornLogger(Logger):
  158. """Rewrite gunicorn default logger."""
  159. def __init__(self, cfg):
  160. self.access_log = setup_logger('gunicorn', 'access')
  161. self.error_log = setup_logger('gunicorn', 'error')
  162. super(GunicornLogger, self).__init__(cfg)
  163. access_log_path = _get_access_log_path()
  164. error_log_path = _get_error_log_path()
  165. os.chmod(access_log_path, stat.S_IREAD | stat.S_IWRITE)
  166. os.chmod(error_log_path, stat.S_IREAD | stat.S_IWRITE)
  167. def start():
  168. """Start web service."""
  169. errorlog_abspath = _get_error_log_path()
  170. gunicorn_conf_file = os.path.join(WEB_CONFIG_DIR, "gunicorn_conf.py")
  171. cmd = "gunicorn " \
  172. "-b {host}:{port} {app_module} " \
  173. "-c {conf_file} " \
  174. "--logger-class {logger_class} " \
  175. "--access-logformat {log_format}"\
  176. .format(host=settings.HOST,
  177. port=settings.PORT,
  178. conf_file=gunicorn_conf_file,
  179. app_module=MINDBOARD_APP_MODULE,
  180. logger_class=GUNICORN_LOGGER,
  181. log_format=settings.GUNICORN_ACCESS_FORMAT
  182. )
  183. log_size = _get_file_size(errorlog_abspath)
  184. console = setup_logger('mindinsight', 'console', console=True, logfile=False, formatter='%(message)s')
  185. # start server
  186. process = subprocess.Popen(
  187. shlex.split(cmd),
  188. shell=False,
  189. stdin=subprocess.PIPE,
  190. stdout=subprocess.PIPE,
  191. stderr=subprocess.PIPE
  192. )
  193. # sleep 1 second for gunicorn appplication to load modules
  194. time.sleep(1)
  195. # check if gunicorn application is running
  196. if process.poll() is not None:
  197. _, stderr = process.communicate()
  198. for line in stderr.decode().split('\n'):
  199. console.error(line)
  200. else:
  201. state_result = _check_server_start_stat(errorlog_abspath, log_size)
  202. # print gunicorn start state to stdout
  203. console.info('Web address: http://%s:%s%s', settings.HOST, settings.PORT, settings.URL_PATH_PREFIX)
  204. for line in state_result["prompt_message"]:
  205. console.info(line)
  206. if __name__ == '__main__':
  207. start()