Files
NexQuant/rdagent/log/logger.py
T
Linlang c8f1c5364a chore: add a rdagent server with UI & logger storage refinement(#553)
* change_log_object

* lint code

* delete comments

* change_log_object

* change_log_object

* fix import test error

* update code

* update code

* fix bugs

* skip mypy error

* skip mypy error

* skip mypy error

* Start the flask server before running the demo.

* achieve front and back interaction

* fix github-advanced-security comments

* fix github-advanced-security comments

* tmp ignore

* fix CI

* move some logic

* change format

* adjust logic

* log2json changes

* tmp

* fix

* fix bug

* refine log2json between 5 scenarios

* fix

* refine codes

* fix logic

* use localhost

* add loop & all_duration param for old scenario startup

* merge control logic

* add README for server ui api

* update README

* reuse code in logger

* add loop_n and all_duration param

* fix upload

* ui server now use port in setting

* fix port setting

* fix port setting

* fix mypy check

* refine logger and log storage

* fix ruff error

* fix CI

* refine logger, loop, storage

* bind one FileStorage with one logger

* not truncate log storage

* refine LoopBase.load(), use `checkout` instead of `output_path` and `do_truncate`

* clear session folder when loading loop to run

* move component info init step to ExpGen Class

* Update rdagent/utils/workflow.py

* move truncate_session function to LoopBase class

* add checkout param for other scenarios

* fix bug

* move WebStorage to UI

* change web_storage name

* add randomname to requirements

* add typer

* fix requirements

---------

Co-authored-by: WinstonLiyte <1957922024@qq.com>
Co-authored-by: Bowen Xian <xianbowen@outlook.com>
Co-authored-by: you-n-g <you-n-g@users.noreply.github.com>
2025-06-06 18:52:19 +08:00

130 lines
4.1 KiB
Python

import os
import sys
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path
from typing import Generator
from loguru import logger
from psutil import Process
from rdagent.core.utils import SingletonBaseClass, import_class
from .base import Storage
from .conf import LOG_SETTINGS
from .storage import FileStorage
from .utils import get_caller_info
class RDAgentLog(SingletonBaseClass):
"""
The files are organized based on the tag & PID
Here is an example tag
.. code-block::
a
- b
- c
- 123
- common_logs.log
- 1322
- common_logs.log
- 1233
- <timestamp>.pkl
- d
- 1233-673 ...
- 1233-4563 ...
- 1233-365 ...
"""
# TODO: Simplify it to introduce less concepts ( We may merge RDAgentLog, Storage &)
# Solution: Storage => PipeLog, View => PipeLogView, RDAgentLog is an instance of PipeLogger
# PipeLogger.info(...) , PipeLogger.get_resp() to get feedback from frontend.
# def f():
# logger = PipeLog()
# logger.info("<code>")
# feedback = logger.get_reps()
_tag: str = ""
def __init__(self) -> None:
self.storage = FileStorage(LOG_SETTINGS.trace_path)
self.other_storages: list[Storage] = []
for storage, args in LOG_SETTINGS.storages.items():
storage_cls = import_class(storage)
self.other_storages.append(storage_cls(*args))
self.main_pid = os.getpid()
@contextmanager
def tag(self, tag: str) -> Generator[None, None, None]:
if tag.strip() == "":
raise ValueError("Tag cannot be empty.")
if self._tag != "":
tag = "." + tag
# TODO: It may result in error in mutithreading or co-routine
self._tag = self._tag + tag
try:
yield
finally:
self._tag = self._tag[: -len(tag)]
def set_storages_path(self, path: str | Path) -> None:
for storage in [self.storage] + self.other_storages:
if hasattr(storage, "path"):
storage.path = path
def truncate_storages(self, time: datetime) -> None:
for storage in [self.storage] + self.other_storages:
storage.truncate(time=time)
def get_pids(self) -> str:
"""
Returns a string of pids from the current process to the main process.
Split by '-'.
"""
pid = os.getpid()
process = Process(pid)
pid_chain = f"{pid}"
while process.pid != self.main_pid:
parent_pid = process.ppid()
parent_process = Process(parent_pid)
pid_chain = f"{parent_pid}-{pid_chain}"
process = parent_process
return pid_chain
def log_object(self, obj: object, *, tag: str = "") -> None:
# TODO: I think we can merge the log_object function with other normal log methods to make the interface simpler.
caller_info = get_caller_info()
tag = f"{self._tag}.{tag}.{self.get_pids()}".strip(".")
for storage in [self.storage] + self.other_storages:
logp = storage.log(obj, tag=tag)
logger.patch(lambda r: r.update(caller_info)).info(f"Log object to [{storage}], uri: {logp}")
def _log(self, level: str, msg: str, *, tag: str = "", raw: bool = False) -> None:
caller_info = get_caller_info()
tag = f"{self._tag}.{tag}.{self.get_pids()}".strip(".")
if raw:
logger.remove()
logger.add(sys.stderr, format=lambda r: "{message}")
log_func = getattr(logger.patch(lambda r: r.update(caller_info)), level)
log_func(msg)
if raw:
logger.remove()
logger.add(sys.stderr)
def info(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
self._log("info", msg, tag=tag, raw=raw)
def warning(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
self._log("warning", msg, tag=tag, raw=raw)
def error(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
self._log("error", msg, tag=tag, raw=raw)