Files
task-6a1d75e5fd30e81cf3126b1a/main.py
T

90 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import argparse
import os
from typing import Annotated, Literal, Union, List
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
# ---------------------------------------------------------------------------
# 1. Pydantic models Union with discriminator
# ---------------------------------------------------------------------------
class HttpOkEvent(BaseModel):
kind: Literal["ok"] = Field(..., description="Event kind")
status: Literal[200] = Field(..., description="HTTP OK status")
path: str = Field(..., description="Request path")
duration_ms: int = Field(..., description="Duration in milliseconds")
class HttpErrorEvent(BaseModel):
kind: Literal["error"] = Field(..., description="Event kind")
status: int = Field(..., description="HTTP error status")
path: str = Field(..., description="Request path")
error_message: str = Field(..., description="Error message")
ApiEvent = Annotated[Union[HttpOkEvent, HttpErrorEvent], Field(discriminator="kind")]
# ---------------------------------------------------------------------------
# 2. LLM and Parser setup
# ---------------------------------------------------------------------------
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_output_tokens=512)
parser = PydanticOutputParser(pydantic_object=ApiEvent)
chain = RunnablePassthrough() | llm | parser
# ---------------------------------------------------------------------------
# 3. Parsing helper
# ---------------------------------------------------------------------------
def parse_log_line(line: str) -> ApiEvent:
prompt = f"Parse the following log line into JSON:\n{line}\nThe JSON should match one of the following schemas:\n- ok event: {{\"kind\": \"ok\", status: 200, path: string, duration_ms: int}}\n- error event: {{\"kind\": \"error\", status: int, path: string, error_message: string}}\nReturn only the JSON."
result = chain.invoke(prompt)
return result
# ---------------------------------------------------------------------------
# 4. Main logic
# ---------------------------------------------------------------------------
def main():
parser_cli = argparse.ArgumentParser(description="Parse log events into typed objects.")
parser_cli.add_argument("--log", type=str, help="Path to log file or raw log string.")
args = parser_cli.parse_args()
if args.log:
if os.path.exists(args.log):
with open(args.log, "r", encoding="utf-8") as f:
raw = f.read()
else:
raw = args.log
else:
raw = """GET /api/users 200 123ms
POST /api/users 404 Not Found
GET /api/orders 500 Internal Server Error
"""
lines = [l.strip() for l in raw.splitlines() if l.strip()]
events: List[ApiEvent] = []
for line in lines:
try:
event = parse_log_line(line)
events.append(event)
except Exception as e:
print(f"Failed to parse line: {line}\nError: {e}")
print("\nParsed Events:")
for ev in events:
print(ev.model_dump())
print("\nTable:\")
header = ["kind", "path", "status"]
print("{:<6} {:<20} {:<6}".format(*header))
for ev in events:
kind = ev.kind
path = getattr(ev, "path", "")
status = getattr(ev, "status", "")
print("{:<6} {:<20} {:<6}".format(kind, path, status))
if __name__ == "__main__":
main()