1
0

healthcheck.py 7.0 KB

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