Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 104 additions & 57 deletions astrbot/core/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
import os
import sys
import threading
import time
from asyncio import Queue
from collections import deque
Expand Down Expand Up @@ -173,6 +174,7 @@ class LogManager:
_console_sink_id: int | None = None
_file_sink_id: int | None = None
_trace_sink_id: int | None = None
_reconfigure_lock = threading.RLock()
_NOISY_LOGGER_LEVELS: dict[str, int] = {
"aiosqlite": logging.WARNING,
"filelock": logging.WARNING,
Expand Down Expand Up @@ -337,40 +339,22 @@ def _add_file_sink(
)

@classmethod
def configure_logger(
def _replace_file_sink(
cls,
*,
logger: logging.Logger,
config: dict | None,
override_level: str | None = None,
enable_file: bool,
file_path: str | None,
max_mb: int | None,
) -> None:
if not config:
return

level = override_level or config.get("log_level")
if level:
try:
logger.setLevel(level)
except Exception:
logger.setLevel(logging.INFO)

if "log_file" in config:
file_conf = config.get("log_file") or {}
enable_file = bool(file_conf.get("enable", False))
file_path = file_conf.get("path")
max_mb = file_conf.get("max_mb")
else:
enable_file = bool(config.get("log_file_enable", False))
file_path = config.get("log_file_path")
max_mb = config.get("log_file_max_mb")

cls._remove_sink(cls._file_sink_id)
cls._file_sink_id = None

if not enable_file:
old_sink_id = cls._file_sink_id
cls._file_sink_id = None
cls._remove_sink(old_sink_id)
return

try:
cls._file_sink_id = cls._add_file_sink(
new_sink_id = cls._add_file_sink(
file_path=cls._resolve_log_path(file_path),
level=logger.level,
max_mb=max_mb,
Expand All @@ -379,39 +363,102 @@ def configure_logger(
)
except Exception as e:
logger.error(f"Failed to add file sink: {e}")

@classmethod
def configure_trace_logger(cls, config: dict | None) -> None:
if not config:
return

enable = bool(
config.get("trace_log_enable")
or (config.get("log_file", {}) or {}).get("trace_enable", False)
)
path = config.get("trace_log_path")
max_mb = config.get("trace_log_max_mb")
if "log_file" in config:
legacy = config.get("log_file") or {}
path = path or legacy.get("trace_path")
max_mb = max_mb or legacy.get("trace_max_mb")

trace_logger = logging.getLogger("astrbot.trace")
cls._ensure_logger_enricher_filter(trace_logger)
cls._ensure_logger_intercept_handler(trace_logger)
trace_logger.setLevel(logging.INFO)
trace_logger.propagate = False

cls._remove_sink(cls._trace_sink_id)
cls._trace_sink_id = None
old_sink_id = cls._file_sink_id
cls._file_sink_id = new_sink_id
cls._remove_sink(old_sink_id)

@classmethod
def _replace_trace_sink(
cls,
*,
enable: bool,
path: str | None,
max_mb: int | None,
) -> None:
if not enable:
old_sink_id = cls._trace_sink_id
cls._trace_sink_id = None
cls._remove_sink(old_sink_id)
return

cls._trace_sink_id = cls._add_file_sink(
file_path=cls._resolve_log_path(path or "logs/astrbot.trace.log"),
level=logging.INFO,
max_mb=max_mb,
backup_count=3,
trace=True,
)
try:
new_sink_id = cls._add_file_sink(
file_path=cls._resolve_log_path(path or "logs/astrbot.trace.log"),
level=logging.INFO,
max_mb=max_mb,
backup_count=3,
trace=True,
)
except Exception as e:
logging.getLogger("astrbot").error(f"Failed to add trace sink: {e}")
Comment thread
Sisyphbaous-DT-Project marked this conversation as resolved.
return

old_sink_id = cls._trace_sink_id
cls._trace_sink_id = new_sink_id
cls._remove_sink(old_sink_id)

@classmethod
def configure_logger(
cls,
logger: logging.Logger,
config: dict | None,
override_level: str | None = None,
) -> None:
with cls._reconfigure_lock:
if not config:
return

level = override_level or config.get("log_level")
if level:
try:
logger.setLevel(level)
except Exception:
logger.setLevel(logging.INFO)

if "log_file" in config:
file_conf = config.get("log_file") or {}
enable_file = bool(file_conf.get("enable", False))
file_path = file_conf.get("path")
max_mb = file_conf.get("max_mb")
else:
enable_file = bool(config.get("log_file_enable", False))
file_path = config.get("log_file_path")
max_mb = config.get("log_file_max_mb")

cls._replace_file_sink(
logger=logger,
enable_file=enable_file,
file_path=file_path,
max_mb=max_mb,
)

@classmethod
def configure_trace_logger(cls, config: dict | None) -> None:
with cls._reconfigure_lock:
if not config:
return

enable = bool(
config.get("trace_log_enable")
or (config.get("log_file", {}) or {}).get("trace_enable", False)
)
path = config.get("trace_log_path")
max_mb = config.get("trace_log_max_mb")
if "log_file" in config:
legacy = config.get("log_file") or {}
path = path or legacy.get("trace_path")
max_mb = max_mb or legacy.get("trace_max_mb")

trace_logger = logging.getLogger("astrbot.trace")
cls._ensure_logger_enricher_filter(trace_logger)
cls._ensure_logger_intercept_handler(trace_logger)
trace_logger.setLevel(logging.INFO)
trace_logger.propagate = False

cls._replace_trace_sink(
enable=enable,
path=path,
max_mb=max_mb,
)
95 changes: 92 additions & 3 deletions astrbot/dashboard/routes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from quart import request

from astrbot.core import astrbot_config, file_token_service, logger
from astrbot.core import LogManager, astrbot_config, file_token_service, logger
from astrbot.core.config.astrbot_config import AstrBotConfig
from astrbot.core.config.default import (
CONFIG_METADATA_2,
Expand Down Expand Up @@ -39,6 +39,81 @@

MAX_FILE_BYTES = 500 * 1024 * 1024

_RUNTIME_LOG_KEYS = (
Comment thread
Sisyphbaous-DT-Project marked this conversation as resolved.
"log_level",
"log_file_enable",
"log_file_path",
"log_file_max_mb",
)

_RUNTIME_TRACE_LOG_KEYS = (
"trace_log_enable",
"trace_log_path",
"trace_log_max_mb",
)


def _runtime_log_config(conf: dict) -> dict:
legacy = conf.get("log_file") or {}
return {
**{key: copy.deepcopy(conf.get(key)) for key in _RUNTIME_LOG_KEYS},
Comment thread
Sisyphbaous-DT-Project marked this conversation as resolved.
"legacy_log_file": {
"enable": copy.deepcopy(legacy.get("enable")),
"path": copy.deepcopy(legacy.get("path")),
"max_mb": copy.deepcopy(legacy.get("max_mb")),
},
}


def _runtime_trace_log_config(conf: dict) -> dict:
legacy = conf.get("log_file") or {}
return {
**{key: copy.deepcopy(conf.get(key)) for key in _RUNTIME_TRACE_LOG_KEYS},
"legacy_log_file": {
"trace_enable": copy.deepcopy(legacy.get("trace_enable")),
"trace_path": copy.deepcopy(legacy.get("trace_path")),
"trace_max_mb": copy.deepcopy(legacy.get("trace_max_mb")),
},
}


def _apply_runtime_log_config_if_changed(
old_config: dict,
new_config: dict,
) -> None:
old_log_config = _runtime_log_config(old_config)
new_log_config = _runtime_log_config(new_config)
old_trace_config = _runtime_trace_log_config(old_config)
new_trace_config = _runtime_trace_log_config(new_config)

if old_log_config == new_log_config and old_trace_config == new_trace_config:
return
Comment thread
Sisyphbaous-DT-Project marked this conversation as resolved.
Outdated

updated = False

if old_log_config != new_log_config:
try:
LogManager.configure_logger(logger, new_config)
updated = True
except Exception:
logger.error(
"Failed to update runtime logger:\n%s",
traceback.format_exc(),
)

if old_trace_config != new_trace_config:
try:
LogManager.configure_trace_logger(new_config)
updated = True
except Exception:
logger.error(
"Failed to update runtime trace logger:\n%s",
traceback.format_exc(),
)

if updated:
logger.info("Runtime log configuration updated.")


def try_cast(value: Any, type_: str):
if type_ == "int":
Expand Down Expand Up @@ -300,10 +375,15 @@ async def _validate_neo_connectivity(


def save_config(
post_config: dict, config: AstrBotConfig, is_core: bool = False
post_config: dict,
config: AstrBotConfig,
is_core: bool = False,
old_config_snapshot: dict | None = None,
) -> None:
"""验证并保存配置"""
errors = None
if is_core and old_config_snapshot is None:
old_config_snapshot = copy.deepcopy(dict(config))

# Snapshot old Computer config for change detection
if is_core:
Expand All @@ -329,6 +409,9 @@ def save_config(

config.save_config(post_config)

if is_core and old_config_snapshot is not None:
_apply_runtime_log_config_if_changed(old_config_snapshot, dict(config))


class ConfigRoute(Route):
def __init__(
Expand Down Expand Up @@ -1521,14 +1604,20 @@ async def _save_astrbot_configs(
if conf_id not in self.acm.confs:
raise ValueError(f"配置文件 {conf_id} 不存在")
astrbot_config = self.acm.confs[conf_id]
old_config_snapshot = copy.deepcopy(dict(astrbot_config))

# 保留服务端的 t2i_active_template 值
if "t2i_active_template" in astrbot_config:
post_configs["t2i_active_template"] = astrbot_config[
"t2i_active_template"
]

save_config(post_configs, astrbot_config, is_core=True)
save_config(
post_configs,
astrbot_config,
is_core=True,
old_config_snapshot=old_config_snapshot,
)
except Exception as e:
raise e

Expand Down
Loading
Loading