Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
30 changes: 24 additions & 6 deletions astrbot/core/utils/storage_cleaner.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
import stat
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from pathlib import Path
Expand Down Expand Up @@ -82,7 +83,7 @@ def cleanup(self, target: str = "all") -> dict:

def _build_status(self, target: str) -> dict:
if target == self.TARGET_LOGS:
files = self._collect_log_files()
files = self.collect_log_files()
primary_path = self._data_dir / "logs"
elif target == self.TARGET_CACHE:
files = self._collect_cache_files()
Expand Down Expand Up @@ -130,7 +131,7 @@ def _cleanup_target(self, target: str) -> dict:
"failed_files": 0,
}
if target == self.TARGET_LOGS:
files = self._collect_log_files()
files = self.collect_log_files()
active_log_files = self._active_log_files()
elif target == self.TARGET_CACHE:
files = self._collect_cache_files()
Expand Down Expand Up @@ -188,7 +189,13 @@ def _cleanup_target(self, target: str) -> dict:
"failed_files": failed_files,
}

def _collect_log_files(self) -> set[Path]:
def collect_log_files(self) -> set[Path]:
"""Collect files under data/logs plus the configured log files.

Returns:
The log files, including rotated ones of the configured log and
trace log paths, even when those are outside data/logs.
"""
files = set(self._iter_files(self._data_dir / "logs"))
for log_path in self._configured_log_paths():
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
files.update(self._iter_log_family_files(log_path))
Expand All @@ -208,10 +215,21 @@ def _collect_log_files(self) -> set[Path]:
or path.name.endswith(("-wal", "-shm", "-journal"))
):
continue
if path.is_symlink() or path.resolve() != path:
candidates.add(path)
continue
try:
# Protect custom database names, including an old active path
# after a configuration edit that has not restarted the core.
with path.open("rb") as stream:
flags = (
os.O_RDONLY
| getattr(os, "O_NOFOLLOW", 0)
| getattr(os, "O_NONBLOCK", 0)
| getattr(os, "O_BINARY", 0)
)
with open(os.open(path, flags), "rb") as stream:
if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode):
continue
if stream.read(16) == b"SQLite format 3\x00":
continue
except OSError:
Expand Down Expand Up @@ -281,8 +299,8 @@ def _resolve_log_path(
path_value = configured_path or default_relative_path
path = Path(path_value)
if path.is_absolute():
return path.resolve()
return (self._data_dir / path).resolve()
return Path(os.path.abspath(path))
return Path(os.path.abspath(self._data_dir / path))

def _iter_log_family_files(self, log_path: Path) -> set[Path]:
files: set[Path] = set()
Expand Down
2 changes: 1 addition & 1 deletion astrbot/dashboard/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def create_dashboard_asgi_app(
files=FileService(),
knowledge_bases=KnowledgeBaseService(core_lifecycle),
live_chat=LiveChatService(db, core_lifecycle),
logs=LogService(log_broker, core_lifecycle.astrbot_config),
logs=LogService(log_broker, core_lifecycle.astrbot_config, core=core_lifecycle),
bots=BotConfigService(core_lifecycle),
platforms=PlatformService(core_lifecycle),
providers=ProviderConfigService(core_lifecycle),
Expand Down
25 changes: 24 additions & 1 deletion astrbot/dashboard/api/logs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations

import asyncio
import time

from fastapi import APIRouter, Depends, Header, Path, Query, Request
from fastapi.responses import StreamingResponse
from fastapi.responses import FileResponse, StreamingResponse
from starlette.background import BackgroundTask

from astrbot.dashboard.responses import ApiError, ok
from astrbot.dashboard.schemas import TraceSettingsRequest
Expand Down Expand Up @@ -80,6 +84,25 @@ async def live_logs(
return _log_stream_response(last_event_id, service)


@router.get("/logs/export")
async def export_logs(
redact: bool = Query(default=True),
_username: str = Depends(require_dashboard_user),
service: LogService = Depends(get_service),
):
try:
archive_path = await asyncio.to_thread(service.export_logs, redact=redact)
except LogServiceError as exc:
_raise_log_error(exc)
return FileResponse(
archive_path,
media_type="application/zip",
filename=f"astrbot-diagnostics-{'raw-' if not redact else ''}{time.strftime('%Y%m%d-%H%M%S')}.zip",
headers={"Cache-Control": "no-store", "X-Content-Type-Options": "nosniff"},
background=BackgroundTask(archive_path.unlink, missing_ok=True),
)


@router.get("/trace/settings")
async def get_trace_settings(
_auth: AuthContext = Depends(require_system_scope),
Expand Down
Loading
Loading