Files
NexQuant/rdagent/log/storage.py
T

149 lines
5.2 KiB
Python
Raw Normal View History

2024-07-16 20:35:42 +08:00
import json
import pickle
import re
2024-07-17 15:00:13 +08:00
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Generator, Literal, Union, cast
2024-07-16 20:35:42 +08:00
2024-07-19 14:16:54 +08:00
from .base import Message, Storage
2024-07-16 20:35:42 +08:00
2024-07-25 15:20:04 +08:00
LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
2024-07-17 15:00:13 +08:00
2024-07-16 20:35:42 +08:00
class FileStorage(Storage):
"""
The info are logginged to the file systems
TODO: describe the storage format
"""
2024-07-25 15:20:04 +08:00
def __init__(self, path: str | Path = "./log/") -> None:
2024-07-16 20:35:42 +08:00
self.path = Path(path)
self.path.mkdir(parents=True, exist_ok=True)
2024-07-17 15:00:13 +08:00
def log(
self,
obj: object,
name: str = "",
save_type: Literal["json", "text", "pkl"] = "text",
timestamp: datetime | None = None,
2024-07-25 15:20:04 +08:00
**kwargs: Any,
) -> Union[str, Path]:
2024-07-16 20:35:42 +08:00
# TODO: We can remove the timestamp after we implement PipeLog
if timestamp is None:
timestamp = datetime.now(timezone.utc)
else:
timestamp = timestamp.astimezone(timezone.utc)
2024-07-17 15:00:13 +08:00
2024-07-16 20:35:42 +08:00
cur_p = self.path / name.replace(".", "/")
cur_p.mkdir(parents=True, exist_ok=True)
path = cur_p / f"{timestamp.strftime('%Y-%m-%d_%H-%M-%S-%f')}.log"
if save_type == "json":
path = path.with_suffix(".json")
with path.open("w") as f:
try:
json.dump(obj, f)
except TypeError:
json.dump(json.loads(str(obj)), f)
return path
elif save_type == "pkl":
path = path.with_suffix(".pkl")
with path.open("wb") as f:
pickle.dump(obj, f)
return path
elif save_type == "text":
obj = str(obj)
with path.open("w") as f:
f.write(obj)
return path
2024-07-19 14:16:54 +08:00
log_pattern = re.compile(
r"(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| "
r"(?P<level>DEBUG|INFO|WARNING|ERROR|CRITICAL) *\| "
r"(?P<caller>.+:.+:\d+) - "
)
2024-07-19 14:16:54 +08:00
def iter_msg(self, watch: bool = False) -> Generator[Message, None, None]:
msg_l = []
for file in self.path.glob("**/*.log"):
tag = ".".join(str(file.relative_to(self.path)).replace("/", ".").split(".")[:-3])
2024-07-19 14:16:54 +08:00
pid = file.parent.name
with file.open("r") as f:
content = f.read()
matches, next_matches = self.log_pattern.finditer(content), self.log_pattern.finditer(content)
2024-07-19 14:16:54 +08:00
next_match = next(next_matches, None)
# NOTE: the content will be the text between `match` and `next_match`
for match in matches:
next_match = next(next_matches, None)
timestamp_str = match.group("timestamp")
timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=timezone.utc)
2024-07-25 15:20:04 +08:00
level: LOG_LEVEL = cast(LOG_LEVEL, match.group("level"))
2024-07-19 14:16:54 +08:00
caller = match.group("caller")
# Extract the message content
message_start = match.end()
message_end = next_match.start() if next_match else len(content)
message_content = content[message_start:message_end].strip()
2024-07-30 17:23:05 +08:00
if "Logging object in" in message_content:
continue
2024-07-19 14:16:54 +08:00
m = Message(
tag=tag, level=level, timestamp=timestamp, caller=caller, pid_trace=pid, content=message_content
2024-07-19 14:16:54 +08:00
)
msg_l.append(m)
2024-07-30 17:23:05 +08:00
for file in self.path.glob("**/*.pkl"):
tag = ".".join(str(file.relative_to(self.path)).replace("/", ".").split(".")[:-3])
pid = file.parent.name
with file.open("rb") as f:
content = pickle.load(f)
timestamp = datetime.strptime(file.stem, "%Y-%m-%d_%H-%M-%S-%f").replace(tzinfo=timezone.utc)
m = Message(tag=tag, level="INFO", timestamp=timestamp, caller="", pid_trace=pid, content=content)
msg_l.append(m)
2024-07-19 14:16:54 +08:00
msg_l.sort(key=lambda x: x.timestamp)
for m in msg_l:
yield m
def truncate(self, time: datetime) -> None:
# any message later than `time` will be removed
for file in self.path.glob("**/*.log"):
with file.open("r") as f:
content = f.read()
new_content = ""
matches, next_matches = self.log_pattern.finditer(content), self.log_pattern.finditer(content)
next_match = next(next_matches, None)
for match in matches:
next_match = next(next_matches, None)
timestamp_str = match.group("timestamp")
timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=timezone.utc)
log_start = match.start()
log_end = next_match.start() if next_match else len(content)
msg = content[match.end() : log_end].strip()
if timestamp > time:
if "Logging object in" in msg:
absolute_p = msg.split("Logging object in ")[1]
p = Path(absolute_p)
p.unlink()
continue
new_content += content[log_start:log_end]
with file.open("w") as f:
f.write(new_content)