Compare commits

..

7 Commits

Author SHA1 Message Date
ef3c310c08 Freeze requirements, ignore settings.py 2026-02-07 21:21:56 +00:00
0da71b7398 refactor: Remove aiodocker, use aiohttp for Docker REST API
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-07 14:18:54 -07:00
45c38a8496 fix: Configure aiodocker client with long timeout for log stream
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-07 14:16:55 -07:00
e89b578931 fix: Configure aiodocker with custom session to disable log timeouts
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-07 14:14:59 -07:00
81bdc71a44 fix: Revert custom aiohttp session and set aiodocker log timeout
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-07 13:06:56 -07:00
a411cb2b5e fix: Prevent aiodocker log stream from timing out
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-07 12:43:03 -07:00
811a9228c0 fix: Log full stack trace for unexpected errors
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-07 12:34:33 -07:00
3 changed files with 58 additions and 34 deletions

1
.gitignore vendored
View File

@@ -151,3 +151,4 @@ out.*
*.txt
*.json
.aider*
settings.py

60
main.py
View File

@@ -7,7 +7,6 @@ logging.basicConfig(
import asyncio
import re
from datetime import datetime, timedelta, timezone
import aiodocker
import aiohttp
import random
import string
@@ -152,20 +151,42 @@ async def main():
"""
Monitors Navidrome container logs for rapid star/unstar events.
"""
docker = None
session = None
try:
docker = aiodocker.Docker()
container = await docker.containers.get(settings.NAVIDROME_CONTAINER)
connector = aiohttp.UnixConnector(path="/var/run/docker.sock")
# Disable all timeouts for the long-polling log stream
timeout = aiohttp.ClientTimeout(total=None, sock_read=None)
session = aiohttp.ClientSession(connector=connector, timeout=timeout)
logging.info(f"Monitoring logs for container '{settings.NAVIDROME_CONTAINER}'...")
logs = container.log(
stdout=True,
stderr=True,
follow=True,
since=datetime.now(timezone.utc).timestamp()
)
container_name = settings.NAVIDROME_CONTAINER
async for line in logs:
# First, check if the container exists.
inspect_url = f"http://localhost/containers/{container_name}/json"
async with session.get(inspect_url) as response:
if response.status == 404:
logging.error(f"Container '{container_name}' not found.")
return
response.raise_for_status()
logging.info(f"Monitoring logs for container '{container_name}'...")
since = int(datetime.now(timezone.utc).timestamp())
params = {
'stdout': 'true',
'stderr': 'true',
'follow': 'true',
'since': str(since),
}
logs_url = f"http://localhost/containers/{container_name}/logs"
async with session.get(logs_url, params=params) as response:
response.raise_for_status()
while True:
line_bytes = await response.content.readline()
if not line_bytes:
break
# Docker's log stream is multiplexed. The first 8 bytes are a header.
# We strip it to get the raw log line.
if len(line_bytes) > 8:
line = line_bytes[8:].decode('utf-8', errors='replace').strip()
parsed = parse_log_line(line)
if not parsed:
continue
@@ -184,16 +205,19 @@ async def main():
# Remove song from tracking after it has been unstarred
del starred_songs[song_id]
except aiodocker.exceptions.DockerError as e:
except aiohttp.ClientResponseError as e:
if e.status == 404:
logging.error(f"Container '{settings.NAVIDROME_CONTAINER}' not found.")
else:
logging.error(f"Error connecting to Docker or getting container: {e}")
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
logging.error(f"Error with Docker API: {e}")
except aiohttp.ClientError as e:
# For other client errors like connection issues
logging.error(f"Error connecting to Docker: {e}")
except Exception:
logging.exception("An unexpected error occurred")
finally:
if docker:
await docker.close()
if session:
await session.close()
if __name__ == "__main__":

View File

@@ -1,4 +1,3 @@
aiodocker==0.24.0
aiohappyeyeballs==2.6.1
aiohttp==3.13.3
aiosignal==1.4.0