diff --git a/src/iSponsorBlockTV/debug_helpers.py b/src/iSponsorBlockTV/debug_helpers.py new file mode 100644 index 0000000..9882196 --- /dev/null +++ b/src/iSponsorBlockTV/debug_helpers.py @@ -0,0 +1,25 @@ + + +class AiohttpTracer: + def __init__(self, logger): + self.logger = logger + + async def on_request_start(self, session, context, params): + self.logger.debug(f"Request started ({id(context):#x}): {params.method} {params.url}") + + async def on_request_end(self, session, context, params): + self.logger.debug(f"Request ended ({id(context):#x}): {params.response.status}") + + async def on_request_exception(self, session, context, params): + self.logger.debug(f"Request exception ({id(context):#x}): {params.exception}") + + async def on_response_chunk_received(self, session, context, params): + chunk_size = len(params.chunk) + try: + # Try to decode as text + text = params.chunk.decode('utf-8') + self.logger.debug(f"Response chunk ({id(context):#x}) {chunk_size} bytes: {text}") + except UnicodeDecodeError: + # If not valid UTF-8, show as hex + hex_data = params.chunk.hex() + self.logger.debug(f"Response chunk ({id(context):#x}) ({chunk_size} bytes) [HEX]: {hex_data}") diff --git a/src/iSponsorBlockTV/helpers.py b/src/iSponsorBlockTV/helpers.py index 6d56925..e06e1bc 100644 --- a/src/iSponsorBlockTV/helpers.py +++ b/src/iSponsorBlockTV/helpers.py @@ -131,6 +131,7 @@ class Config: help="data directory", ) @click.option("--debug", is_flag=True, help="debug mode") +@click.option("--http-tracing", is_flag=True, help="Enable HTTP request/response tracing") # legacy commands as arguments @click.option("--setup", is_flag=True, help="Setup the program graphically", hidden=True) @click.option( @@ -140,11 +141,12 @@ class Config: hidden=True, ) @click.pass_context -def cli(ctx, data, debug, setup, setup_cli): +def cli(ctx, data, debug, http_tracing, setup, setup_cli): """iSponsorblockTV""" ctx.ensure_object(dict) ctx.obj["data_dir"] = data ctx.obj["debug"] = debug + ctx.obj["http_tracing"] = http_tracing logger = logging.getLogger() ctx.obj["logger"] = logger @@ -189,7 +191,7 @@ def start(ctx): """Start the main program""" config = Config(ctx.obj["data_dir"]) config.validate() - main.main(config, ctx.obj["debug"]) + main.main(config, ctx.obj["debug"], ctx.obj["http_tracing"]) # Create fake "self" group to show pyapp options in help menu diff --git a/src/iSponsorBlockTV/main.py b/src/iSponsorBlockTV/main.py index 8363f36..2172403 100644 --- a/src/iSponsorBlockTV/main.py +++ b/src/iSponsorBlockTV/main.py @@ -7,6 +7,7 @@ from typing import Optional import aiohttp from . import api_helpers, ytlounge +from .debug_helpers import AiohttpTracer class DeviceListener: @@ -153,14 +154,28 @@ def handle_signal(signum, frame): raise KeyboardInterrupt() -async def main_async(config, debug): +async def main_async(config, debug, http_tracing): loop = asyncio.get_event_loop_policy().get_event_loop() tasks = [] # Save the tasks so the interpreter doesn't garbage collect them devices = [] # Save the devices to close them later if debug: loop.set_debug(True) + tcp_connector = aiohttp.TCPConnector(ttl_dns_cache=300) - web_session = aiohttp.ClientSession(connector=tcp_connector) + + # Configure session with tracing if enabled + if http_tracing: + root_logger = logging.getLogger("aiohttp_trace") + tracer = AiohttpTracer(root_logger) + trace_config = aiohttp.TraceConfig() + trace_config.on_request_start.append(tracer.on_request_start) + trace_config.on_response_chunk_received.append(tracer.on_response_chunk_received) + trace_config.on_request_end.append(tracer.on_request_end) + trace_config.on_request_exception.append(tracer.on_request_exception) + web_session = aiohttp.ClientSession(connector=tcp_connector, trace_configs=[trace_config]) + else: + web_session = aiohttp.ClientSession(connector=tcp_connector) + api_helper = api_helpers.ApiHelper(config, web_session) for i in config.devices: device = DeviceListener(api_helper, config, i, debug, web_session) @@ -184,5 +199,5 @@ async def main_async(config, debug): print("Exited") -def main(config, debug): - asyncio.run(main_async(config, debug)) +def main(config, debug, http_tracing): + asyncio.run(main_async(config, debug, http_tracing))