ray/dashboard/modules/log/log_head.py

105 lines
3.8 KiB
Python

import logging
import aiohttp.web
import ray.dashboard.modules.log.log_utils as log_utils
import ray.dashboard.utils as dashboard_utils
import ray.dashboard.optional_utils as dashboard_optional_utils
from ray.dashboard.datacenter import DataSource, GlobalSignals
logger = logging.getLogger(__name__)
routes = dashboard_optional_utils.ClassMethodRouteTable
class LogHead(dashboard_utils.DashboardHeadModule):
LOG_URL_TEMPLATE = "http://{ip}:{port}/logs"
def __init__(self, dashboard_head):
super().__init__(dashboard_head)
# We disable auto_decompress when forward / proxy log url.
self._proxy_session = aiohttp.ClientSession(auto_decompress=False)
log_utils.register_mimetypes()
routes.static("/logs", self._dashboard_head.log_dir, show_index=True)
GlobalSignals.node_info_fetched.append(self.insert_log_url_to_node_info)
GlobalSignals.node_summary_fetched.append(self.insert_log_url_to_node_info)
async def insert_log_url_to_node_info(self, node_info):
node_id = node_info.get("raylet", {}).get("nodeId")
if node_id is None:
return
agent_port = DataSource.agents.get(node_id)
if agent_port is None:
return
agent_http_port, _ = agent_port
log_url = self.LOG_URL_TEMPLATE.format(
ip=node_info.get("ip"), port=agent_http_port
)
node_info["logUrl"] = log_url
@routes.get("/log_index")
async def get_log_index(self, req) -> aiohttp.web.Response:
url_list = []
agent_ips = []
for node_id, ports in DataSource.agents.items():
ip = DataSource.node_id_to_ip[node_id]
agent_ips.append(ip)
url_list.append(self.LOG_URL_TEMPLATE.format(ip=ip, port=str(ports[0])))
if self._dashboard_head.ip not in agent_ips:
url_list.append(
self.LOG_URL_TEMPLATE.format(
ip=self._dashboard_head.ip, port=self._dashboard_head.http_port
)
)
return aiohttp.web.Response(
text=self._directory_as_html(url_list), content_type="text/html"
)
@routes.get("/log_proxy")
async def get_log_from_proxy(self, req) -> aiohttp.web.StreamResponse:
url = req.query.get("url")
if not url:
raise Exception("url is None.")
body = await req.read()
# Special logic to handle hashtags only. The only character that
# is not getting properly encoded by aiohttp's static file server
encoded_url = url.replace("#", "%23")
async with self._proxy_session.request(
req.method, encoded_url, data=body, headers=req.headers
) as r:
sr = aiohttp.web.StreamResponse(
status=r.status, reason=r.reason, headers=req.headers
)
sr.content_length = r.content_length
sr.content_type = r.content_type
sr.charset = r.charset
writer = await sr.prepare(req)
async for data in r.content.iter_any():
await writer.write(data)
return sr
@staticmethod
def _directory_as_html(url_list) -> str:
# returns directory's index as html
index_of = "Index of logs"
h1 = f"<h1>{index_of}</h1>"
index_list = []
for url in sorted(url_list):
index_list.append(f'<li><a href="{url}">{url}</a></li>')
index_list = "\n".join(index_list)
ul = f"<ul>\n{index_list}\n</ul>"
body = f"<body>\n{h1}\n{ul}\n</body>"
head_str = f"<head>\n<title>{index_of}</title>\n</head>"
html = f"<html>\n{head_str}\n{body}\n</html>"
return html
async def run(self, server):
pass
@staticmethod
def is_minimal_module():
return False