diff --git a/.logging_config.yaml b/.logging_config.yaml index bf4c8c5..f7cccf9 100644 --- a/.logging_config.yaml +++ b/.logging_config.yaml @@ -48,6 +48,7 @@ handlers: class: control_backend.logging.DatedFileHandler formatter: experiment filters: [partial] + # Directory must match config.logging_settings.experiment_log_directory file_prefix: experiment_logs/experiment # Level for external libraries @@ -59,6 +60,6 @@ loggers: control_backend: level: LLM handlers: [ui] - experiment: + experiment: # This name must match config.logging_settings.experiment_logger_name level: DEBUG handlers: [ui, file] diff --git a/src/control_backend/api/v1/endpoints/logs.py b/src/control_backend/api/v1/endpoints/logs.py index ccccf44..0e2dff9 100644 --- a/src/control_backend/api/v1/endpoints/logs.py +++ b/src/control_backend/api/v1/endpoints/logs.py @@ -1,8 +1,9 @@ import logging +from pathlib import Path import zmq -from fastapi import APIRouter -from fastapi.responses import StreamingResponse +from fastapi import APIRouter, HTTPException +from fastapi.responses import FileResponse, StreamingResponse from zmq.asyncio import Context from control_backend.core.config import settings @@ -38,3 +39,29 @@ async def log_stream(): yield f"data: {message}\n\n" return StreamingResponse(gen(), media_type="text/event-stream") + + +LOGGING_DIR = Path(settings.logging_settings.experiment_log_directory).resolve() + + +@router.get("/logs/files") +@router.get("/api/logs/files") +async def log_directory(): + """ + Get a list of all log files stored in the experiment log file directory. + """ + return [f.name for f in LOGGING_DIR.glob("*.log")] + + +@router.get("/logs/files/{filename}") +@router.get("/api/logs/files/{filename}") +async def log_file(filename: str): + # Prevent path-traversal + file_path = (LOGGING_DIR / filename).resolve() # This .resolve() is important + if not file_path.is_relative_to(LOGGING_DIR): + raise HTTPException(status_code=400, detail="Invalid filename.") + + if not file_path.is_file(): + raise HTTPException(status_code=404, detail="File not found.") + + return FileResponse(file_path, filename=file_path.name) diff --git a/src/control_backend/core/config.py b/src/control_backend/core/config.py index c8af094..2dbde02 100644 --- a/src/control_backend/core/config.py +++ b/src/control_backend/core/config.py @@ -147,10 +147,12 @@ class LoggingSettings(BaseModel): Configuration for logging. :ivar logging_config_file: Path to the logging configuration file. - :ivar experiment_logger_name: Name of the experiment logger, should match the logging config. + :ivar experiment_log_directory: Location of the experiment logs. Must match the logging config. + :ivar experiment_logger_name: Name of the experiment logger. Must match the logging config. """ logging_config_file: str = ".logging_config.yaml" + experiment_log_directory: str = "experiment_logs" experiment_logger_name: str = "experiment"