# -*- coding: utf-8 -*- """Разбор логов вылетов Minecraft. Ищет в логе последний exception-блок и определяет причину: нехватка ОЗУ, проблемы с Java, конкретный мод или «неизвестно». """ import re # "java.lang.NullPointerException: ..." / "Caused by: ...Exception: ..." _EXC = re.compile( r"^(?:Caused by: |Exception in thread [^\s]+ )?" r"([\w.$]+(?:Exception|Error|Throwable))(?::\s*(.*))?$") _MOD_JAR = re.compile(r"mods[/\\]([\w.\- ]+\.jar)") _MOD_AT = re.compile(r"\(([\w\-]+)@[\d.]+\)") # SomeMod@1.2.3 в кадрах _XMX = re.compile(r"-xmx(\d+)([mg])") def analyze(text=None, path=None, tail=600): """Разбирает лог. Возвращает dict: kind — "crash" | "oom" | "java" | "unknown" exc — класс исключения (без пакета) msg — сообщение исключения culprit — имя файла мода (mods/xxx.jar) или id мода, если найдено hints — подсказки ("oom", "xmx=2048", "classver", ...) """ res = {"kind": "unknown", "exc": "", "msg": "", "culprit": None, "hints": []} lines = [] if text is not None: lines = text.splitlines()[-tail:] elif path: try: with open(path, encoding="utf-8", errors="replace") as f: lines = f.readlines()[-tail:] except OSError: pass joined = "\n".join(lines) if not joined.strip(): return res low = joined.lower() # --- нехватка ОЗУ if ("outofmemoryerror" in low or "could not reserve enough space" in low or "gc overhead limit exceeded" in low): res["kind"] = "oom" res["exc"] = "OutOfMemoryError" res["hints"].append("oom") m = _XMX.search(low) if m: res["hints"].append("xmx=" + m.group(1) + m.group(2)) return res # --- проблемы с Java for pat, tag in ((r"unsupported class file major version (\d+)", "classver"), (r"unsupportedclassversionerror", "classver"), (r"could not create the java virtual machine", "jvm"), (r"invalid java runtime|no jvm could be found", "java")): if re.search(pat, low): res["kind"] = "java" res["exc"] = tag res["hints"].append(tag) return res # --- последний exception-блок idx = [i for i, ln in enumerate(lines) if _EXC.match(ln.strip())] if not idx: return res caused = [i for i in idx if lines[i].strip().startswith("Caused by:")] # корневая причина — последний Caused by в цепочке m = _EXC.match(lines[caused[-1] if caused else idx[0]].strip()) res["kind"] = "crash" if m: res["exc"] = m.group(1).split(".")[-1] res["msg"] = (m.group(2) or "")[:200] # --- виновник: файл мода в тексте или mixin-кадр с mod id m2 = _MOD_JAR.search(joined) if m2: res["culprit"] = m2.group(1) else: m3 = _MOD_AT.search(joined) if m3: res["culprit"] = m3.group(1) return res