130 lines
4.9 KiB
Python
130 lines
4.9 KiB
Python
import os
|
|
import argparse
|
|
from typing import Annotated, Literal, Union, List
|
|
|
|
from pydantic import BaseModel, Field, ValidationError
|
|
from langchain_core.output_parsers import PydanticOutputParser
|
|
from langchain_core.prompts import PromptTemplate
|
|
from langchain_openai import ChatOpenAI
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 1. Модели событий
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
class HttpOkEvent(BaseModel):
|
|
kind: Literal["ok"] = Field(..., description="Тип события: OK")
|
|
status: Literal[200] = Field(..., description="HTTP статус 200")
|
|
path: str = Field(..., description="Запрошенный путь")
|
|
duration_ms: int = Field(..., description="Время выполнения в миллисекундах")
|
|
|
|
class HttpErrorEvent(BaseModel):
|
|
kind: Literal["error"] = Field(..., description="Тип события: ошибка")
|
|
status: int = Field(..., description="HTTP статус 4xx/5xx")
|
|
path: str = Field(..., description="Запрошенный путь")
|
|
error_message: str = Field(..., description="Текст ошибки")
|
|
|
|
ApiEvent = Annotated[
|
|
Union[HttpOkEvent, HttpErrorEvent],
|
|
Field(discriminator="kind")
|
|
]
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 2. Парсер LLM + Pydantic
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
parser = PydanticOutputParser(pydantic_object=ApiEvent)
|
|
|
|
prompt_template = PromptTemplate(
|
|
input_variables=["log_line"],
|
|
template=(
|
|
"Найди в следующем логе событие API и выведи его в формате JSON, "
|
|
"соответствующем одной из следующих схем:\n"
|
|
"1. {ok_schema}\n"
|
|
"2. {error_schema}\n"
|
|
"Укажи поле \"kind\" как \"ok\" или \"error\".\n"
|
|
"Лог: {log_line}\n"
|
|
"Ответ в чистом JSON без комментариев."
|
|
),
|
|
)
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 3. Функция обработки одной строки
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def parse_line(line: str, llm: ChatOpenAI) -> ApiEvent:
|
|
ok_schema = HttpOkEvent.model_json_schema()
|
|
error_schema = HttpErrorEvent.model_json_schema()
|
|
prompt = prompt_template.format(
|
|
log_line=line,
|
|
ok_schema=ok_schema,
|
|
error_schema=error_schema,
|
|
)
|
|
response = llm.invoke(prompt)
|
|
try:
|
|
event = parser.parse(response.content)
|
|
except ValidationError as e:
|
|
raise ValueError(f"LLM не смог распарсить строку: {line}\n{e}") from e
|
|
return event
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 4. Обработка всего лога
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def parse_log(text: str, llm: ChatOpenAI) -> List[ApiEvent]:
|
|
events: List[ApiEvent] = []
|
|
# Разделяем по строкам, игнорируем пустые
|
|
for line in text.strip().splitlines():
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
event = parse_line(line, llm)
|
|
events.append(event)
|
|
return events
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 5. CLI
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def main():
|
|
parser_cli = argparse.ArgumentParser(description="Parse API logs into structured events.")
|
|
parser_cli.add_argument(
|
|
"--log",
|
|
type=str,
|
|
default=None,
|
|
help="Путь к файлу с логом. Если не задан, используется пример.",
|
|
)
|
|
args = parser_cli.parse_args()
|
|
|
|
if args.log:
|
|
with open(args.log, "r", encoding="utf-8") as f:
|
|
log_text = f.read()
|
|
else:
|
|
log_text = """\
|
|
2023-10-01 12:00:01 INFO /api/users 200 123ms
|
|
2023-10-01 12:00:02 ERROR /api/orders 404 Not Found
|
|
2023-10-01 12:00:03 WARN /api/payments 500 Internal Server Error
|
|
"""
|
|
|
|
# Инициализируем LLM
|
|
openai_api_key = os.getenv("OPENAI_API_KEY")
|
|
if not openai_api_key:
|
|
raise RuntimeError("Переменная окружения OPENAI_API_KEY не найдена")
|
|
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=openai_api_key)
|
|
|
|
events = parse_log(log_text, llm)
|
|
|
|
# Выводим каждый объект
|
|
for ev in events:
|
|
print(ev.model_dump())
|
|
|
|
# Таблица
|
|
print("\n| kind | path | status |")
|
|
print("|------|------|--------|")
|
|
for ev in events:
|
|
if ev.kind == "ok":
|
|
print(f"| {ev.kind} | {ev.path} | {ev.status} |")
|
|
else:
|
|
print(f"| {ev.kind} | {ev.path} | {ev.status} |")
|
|
|
|
if __name__ == "__main__":
|
|
main() |