diff --git a/app.py b/app.py index 8104ffc..38baaca 100644 --- a/app.py +++ b/app.py @@ -125,6 +125,37 @@ def docker_inspect_restart_count(container_name: str) -> int: except Exception: return 0 +def get_container_logs(container_name: str, lines: int = 50) -> list[dict]: + """ + Get last N lines of container logs with error detection. + Returns list of dicts with 'text' and 'level' keys. + """ + try: + out = sh([DOCKER, "logs", "--tail", str(lines), container_name]) + log_lines = [] + + for line in out.splitlines(): + # Strip ANSI color codes + line_clean = re.sub(r'\x1b\[[0-9;]*m', '', line) + + # Detect log level + line_lower = line_clean.lower() + if any(x in line_lower for x in ['error', 'exception', 'fatal', 'critical']): + level = 'error' + elif any(x in line_lower for x in ['warn', 'warning']): + level = 'warn' + else: + level = 'info' + + log_lines.append({ + 'text': line_clean, + 'level': level + }) + + return log_lines + except Exception: + return [] + def is_app_web_container(name: str) -> bool: # Dokku apps typically have containers like ".web.1" return name.endswith(".web.1") and not name.startswith("dokku.") @@ -169,6 +200,7 @@ def collect(): "mem_limit": s.get("mem_limit", ""), "mem_pct": s.get("mem_pct", ""), "restarts": docker_inspect_restart_count(name), + "logs": get_container_logs(name, lines=50), } if is_app_web_container(name): diff --git a/templates/apps_table.html b/templates/apps_table.html index 58838e1..2e35896 100644 --- a/templates/apps_table.html +++ b/templates/apps_table.html @@ -366,4 +366,102 @@ - {% endif %} \ No newline at end of file + {% endif %} + + +

[ APPLICATION LOGS ]

+ {% for r in data.apps %} +
+ +
+
[{{ r.app }}]
+ +
+ + + +
+ {% endfor %} + + \ No newline at end of file