healthcheck.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import requests
  2. import socket
  3. import threading
  4. import discord
  5. import math
  6. import logging
  7. from http.server import BaseHTTPRequestHandler, HTTPServer
  8. from datetime import datetime
  9. from tcp_latency import measure_latency
  10. import myanimebot.globals as globals
  11. import myanimebot.utils as utils
  12. import myanimebot.anilist as anilist
  13. webtext = ""
  14. uptime = datetime.now().strftime("%H:%M:%S %d/%m/%Y")
  15. healthcheck_logger = logging.getLogger("healthcheck")
  16. healthcheck_logger.setLevel(globals.logLevel)
  17. class MyServer(BaseHTTPRequestHandler):
  18. def log_message(self, format, *args):
  19. message = format % args
  20. if " 200 " in message:
  21. globals.logger.debug(message)
  22. else:
  23. globals.logger.error(message)
  24. def do_GET(self):
  25. try:
  26. timestamp_request = datetime.now()
  27. webtext = "<html><head><title>MyAnimeBot Healthcheck status</title><link rel='icon' type='image/gif'' href='{}' /></head><body><h1>MyAnimeBot Healthcheck status</h1><table>".format(globals.iconBot)
  28. code = 200
  29. code, webtext = get_version(code, webtext)
  30. code, webtext = get_uptime(code, webtext)
  31. code, webtext = get_db_status(code, webtext)
  32. code, webtext = get_discord_websocket_status(code, webtext)
  33. code, webtext = get_anilist_status(code, webtext)
  34. code, webtext = get_myanimelist_status(code, webtext)
  35. generation_time = (datetime.now() - timestamp_request).total_seconds() * 1000
  36. webtext += "</table><p><em>Healthcheck generated in {}ms.</em></p></body></html>".format(round(generation_time))
  37. except:
  38. webtext = "<html><head><title>MyAnimeBot Healthcheck status</title></head><body><h1>MyAnimeBot Healthcheck status</h1><p>An unexpected error as occured when we tried to generate the healthcheck page, check the logs for more information.</p></body></html>"
  39. code = 503
  40. globals.logger.exception("Error on the healthcheck:\n")
  41. self.send_response(code)
  42. self.send_header("Content-type", "text/html")
  43. self.end_headers()
  44. self.wfile.write(bytes(webtext, "utf-8"))
  45. def line_formatter (desc : str, state : str, level : int):
  46. # Levels : 0 OK, 1 Error, 2 Warning, 3 Disabled
  47. if (level == 0):
  48. color = "7FFF00"
  49. elif (level == 1):
  50. color = "CD5C5C"
  51. elif (level == 2):
  52. color = "FFD700"
  53. else:
  54. color = "888888"
  55. result = "<tr><td>{}: </td><td bgcolor='{}' ><strong>{}</strong></td></tr>".format(desc, color, state)
  56. return result
  57. def ping(hostname : str):
  58. latencies = measure_latency(host=hostname, runs=1, wait=0)
  59. total = 0
  60. for value in latencies:
  61. total += value
  62. result = math.trunc(total/len(latencies))
  63. return result
  64. def get_anilist_status (code : int, webtext : str):
  65. if (globals.ANI_ENABLED):
  66. try:
  67. ani_status_code = requests.post(anilist.ANILIST_GRAPHQL_URL, timeout=3, allow_redirects=False).status_code
  68. if (ani_status_code == 400):
  69. ani_ping = ping("graphql.anilist.co")
  70. if (ani_ping < 300):
  71. webtext += line_formatter("AniList API status", "OK ({}ms)".format(ani_ping), 0)
  72. else:
  73. webtext += line_formatter("AniList API status", "SLOW ({}ms)".format(ani_ping), 2)
  74. else:
  75. webtext += line_formatter("AniList API status", "KO ({})".format(ani_status_code), 1)
  76. if (code == 200): code = 500
  77. except Exception as e:
  78. webtext += line_formatter("AniList API status", "KO ({})".format(e), 1)
  79. if (code == 200): code = 500
  80. else:
  81. webtext += line_formatter("AniList API status", "DISABLED", 3)
  82. return code, webtext
  83. def get_myanimelist_status (code : int, webtext : str):
  84. if (globals.MAL_ENABLED):
  85. try:
  86. mal_status_code = requests.head(globals.MAL_URL, timeout=3, allow_redirects=False).status_code
  87. if (mal_status_code == 200):
  88. mal_ping = ping("myanimelist.net")
  89. if (mal_ping < 300):
  90. webtext += line_formatter("MyAnimeList status", "OK ({}ms)".format(mal_ping), 0)
  91. else:
  92. webtext += line_formatter("MyAnimeList status", "SLOW ({}ms)".format(mal_ping), 2)
  93. else:
  94. webtext += line_formatter("MyAnimeList status", "KO ({})".format(mal_status_code), 1)
  95. if (code == 200): code = 500
  96. except Exception as e:
  97. webtext += line_formatter("MyAnimeList status", "KO ({})".format(e), 1)
  98. if (code == 200): code = 500
  99. else:
  100. webtext += line_formatter("MyAnimeList status", "DISABLED", 3)
  101. return code, webtext
  102. def get_uptime (code : int, webtext : str):
  103. webtext += line_formatter("Script uptime", uptime, 0)
  104. return code, webtext
  105. def get_version (code : int, webtext : str):
  106. webtext += line_formatter("Script version", globals.VERSION, 0)
  107. return code, webtext
  108. def get_discord_websocket_status (code : int, webtext : str):
  109. if (globals.client.is_closed()) or (not globals.client.is_ready()):
  110. webtext += line_formatter("Discord status", "KO", 1)
  111. if (code == 200): code = 500
  112. else:
  113. if (globals.client.is_ws_ratelimited()): webtext += line_formatter("Discord status", "NOT OK (Rate limited)", 2)
  114. else: webtext += line_formatter("Discord status", "OK (v{})".format(discord.__version__), 0)
  115. return code, webtext
  116. def get_db_status (code : int, webtext : str):
  117. try:
  118. cursor = globals.conn.cursor(buffered=False)
  119. cursor.execute("SELECT * FROM t_feeds LIMIT 1;")
  120. cursor.fetchone()
  121. cursor.close()
  122. cursor = globals.conn.cursor(buffered=True, dictionary=True)
  123. cursor.execute("SELECT @@VERSION AS ver;")
  124. data = cursor.fetchone()
  125. cursor.close()
  126. webtext += line_formatter("Database status", "OK ({})".format(data["ver"]), 0)
  127. except Exception as e:
  128. webtext += line_formatter("Database status", "KO", 1)
  129. globals.logger.error("The healthcheck cannot access to the database: {}".format(e))
  130. if (code == 200): code = 500
  131. return code, webtext
  132. def start_healthcheck(ip, port):
  133. webServer = HTTPServer((ip, port), MyServer)
  134. globals.logger.info("Healthcheck started on http://{}:{}".format(ip, port))
  135. try:
  136. webServer.serve_forever()
  137. except KeyboardInterrupt:
  138. pass
  139. except Exception as e:
  140. globals.logger.error("The healthcheck crashed: {}".format(e))
  141. async def main(asyncioloop):
  142. ''' Main function that starts the Healthcheck web page '''
  143. globals.logger.info("Starting up Healtcheck...")
  144. healthcheck_thread = threading.Thread(name='healthcheck', target=start_healthcheck, args=(globals.HEALTHCHECK_IP, globals.HEALTHCHECK_PORT))
  145. healthcheck_thread.setDaemon(True)
  146. healthcheck_thread.start()