From 2cf777cc4c84a92aef155cb2fd8bce86802b300d Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Thu, 24 Apr 2025 23:04:59 +0100 Subject: [PATCH] logger, docker, wip --- backend/{docker => }/Dockerfile | 2 +- backend/api.py | 39 +++++++++++---- backend/docker-compose.yml | 2 +- backend/fly.production.toml | 2 +- backend/fly.staging.toml | 2 +- backend/sandbox/api.py | 11 +++++ backend/utils/logger.py | 85 ++++++++++++++++++++------------- 7 files changed, 98 insertions(+), 45 deletions(-) rename backend/{docker => }/Dockerfile (88%) diff --git a/backend/docker/Dockerfile b/backend/Dockerfile similarity index 88% rename from backend/docker/Dockerfile rename to backend/Dockerfile index d8ff92ee..624d4f51 100644 --- a/backend/docker/Dockerfile +++ b/backend/Dockerfile @@ -20,5 +20,5 @@ ENV ENV_MODE="production" # Expose the port the app runs on EXPOSE 8000 -# Command to run the application with Uvicorn directly +# 24 workers CMD ["gunicorn", "api:app", "--workers", "24", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--timeout", "600", "--graceful-timeout", "300", "--keep-alive", "250", "--max-requests", "0", "--max-requests-jitter", "0", "--forwarded-allow-ips", "*", "--worker-connections", "5000", "--worker-tmp-dir", "/dev/shm", "--preload"] \ No newline at end of file diff --git a/backend/api.py b/backend/api.py index 75de335d..e95c15bc 100644 --- a/backend/api.py +++ b/backend/api.py @@ -83,12 +83,27 @@ async def lifespan(app: FastAPI): app = FastAPI(lifespan=lifespan) -# @app.middleware("http") -# async def log_requests_middleware(request: Request, call_next): -# client_ip = request.client.host -# logger.info(f"Request from IP {client_ip} to {request.method} {request.url.path}") -# response = await call_next(request) -# return response +@app.middleware("http") +async def log_requests_middleware(request: Request, call_next): + start_time = time.time() + client_ip = request.client.host + method = request.method + url = str(request.url) + path = request.url.path + query_params = str(request.query_params) + + # Log the incoming request + logger.info(f"Request started: {method} {path} from {client_ip} | Query: {query_params}") + + try: + response = await call_next(request) + process_time = time.time() - start_time + logger.debug(f"Request completed: {method} {path} | Status: {response.status_code} | Time: {process_time:.2f}s") + return response + except Exception as e: + process_time = time.time() - start_time + logger.error(f"Request failed: {method} {path} | Error: {str(e)} | Time: {process_time:.2f}s") + raise # @app.middleware("http") # async def throw_error_middleware(request: Request, call_next): @@ -167,5 +182,13 @@ async def health_check(): if __name__ == "__main__": import uvicorn - logger.info("Starting server on 0.0.0.0:8000") - uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file + + workers = 2 + + logger.info(f"Starting server on 0.0.0.0:8000 with {workers} workers") + uvicorn.run( + "api:app", + host="0.0.0.0", + port=8000, + workers=workers + ) \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 09c8bb14..b3b6ae82 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -4,7 +4,7 @@ services: api: build: context: . - dockerfile: docker/Dockerfile + dockerfile: Dockerfile ports: - "8000:8000" env_file: diff --git a/backend/fly.production.toml b/backend/fly.production.toml index 4836a458..5c2461d2 100644 --- a/backend/fly.production.toml +++ b/backend/fly.production.toml @@ -7,7 +7,7 @@ app = 'backend-production-ogog' primary_region = 'bos' [build] - dockerfile = 'docker/Dockerfile' + dockerfile = 'Dockerfile' [http_service] internal_port = 8000 diff --git a/backend/fly.staging.toml b/backend/fly.staging.toml index ddc88c70..f51637ab 100644 --- a/backend/fly.staging.toml +++ b/backend/fly.staging.toml @@ -7,7 +7,7 @@ app = 'backend-staging-icy-mountain-363' primary_region = 'cdg' [build] - dockerfile = 'docker/Dockerfile' + dockerfile = 'Dockerfile' [http_service] internal_port = 8000 diff --git a/backend/sandbox/api.py b/backend/sandbox/api.py index d696fc92..2a939747 100644 --- a/backend/sandbox/api.py +++ b/backend/sandbox/api.py @@ -119,6 +119,7 @@ async def create_file( user_id: Optional[str] = Depends(get_optional_user_id) ): """Create a file in the sandbox using direct file upload""" + logger.info(f"Received file upload request for sandbox {sandbox_id}, path: {path}, user_id: {user_id}") client = await db.client # Verify the user has access to this sandbox @@ -149,6 +150,7 @@ async def create_file_json( user_id: Optional[str] = Depends(get_optional_user_id) ): """Create a file in the sandbox using JSON (legacy support)""" + logger.info(f"Received JSON file creation request for sandbox {sandbox_id}, user_id: {user_id}") client = await db.client # Verify the user has access to this sandbox @@ -163,6 +165,7 @@ async def create_file_json( content = file_request.get("content", "") if not path: + logger.error(f"Missing file path in request for sandbox {sandbox_id}") raise HTTPException(status_code=400, detail="File path is required") # Convert string content to bytes @@ -186,6 +189,7 @@ async def list_files( user_id: Optional[str] = Depends(get_optional_user_id) ): """List files and directories at the specified path""" + logger.info(f"Received list files request for sandbox {sandbox_id}, path: {path}, user_id: {user_id}") client = await db.client # Verify the user has access to this sandbox @@ -213,6 +217,7 @@ async def list_files( ) result.append(file_info) + logger.info(f"Successfully listed {len(result)} files in sandbox {sandbox_id}") return {"files": [file.dict() for file in result]} except Exception as e: logger.error(f"Error listing files in sandbox {sandbox_id}: {str(e)}") @@ -226,6 +231,7 @@ async def read_file( user_id: Optional[str] = Depends(get_optional_user_id) ): """Read a file from the sandbox""" + logger.info(f"Received file read request for sandbox {sandbox_id}, path: {path}, user_id: {user_id}") client = await db.client # Verify the user has access to this sandbox @@ -240,6 +246,7 @@ async def read_file( # Return a Response object with the content directly filename = os.path.basename(path) + logger.info(f"Successfully read file {filename} from sandbox {sandbox_id}") return Response( content=content, media_type="application/octet-stream", @@ -259,12 +266,14 @@ async def ensure_project_sandbox_active( Ensure that a project's sandbox is active and running. Checks the sandbox status and starts it if it's not running. """ + logger.info(f"Received ensure sandbox active request for project {project_id}, user_id: {user_id}") client = await db.client # Find the project and sandbox information project_result = await client.table('projects').select('*').eq('project_id', project_id).execute() if not project_result.data or len(project_result.data) == 0: + logger.error(f"Project not found: {project_id}") raise HTTPException(status_code=404, detail="Project not found") project_data = project_result.data[0] @@ -273,6 +282,7 @@ async def ensure_project_sandbox_active( if not project_data.get('is_public'): # For private projects, we must have a user_id if not user_id: + logger.error(f"Authentication required for private project {project_id}") raise HTTPException(status_code=401, detail="Authentication required for this resource") account_id = project_data.get('account_id') @@ -281,6 +291,7 @@ async def ensure_project_sandbox_active( if account_id: account_user_result = await client.schema('basejump').from_('account_user').select('account_role').eq('user_id', user_id).eq('account_id', account_id).execute() if not (account_user_result.data and len(account_user_result.data) > 0): + logger.error(f"User {user_id} not authorized to access project {project_id}") raise HTTPException(status_code=403, detail="Not authorized to access this project") try: diff --git a/backend/utils/logger.py b/backend/utils/logger.py index 16c3a1f9..db7dca53 100644 --- a/backend/utils/logger.py +++ b/backend/utils/logger.py @@ -66,45 +66,64 @@ def setup_logger(name: str = 'agentpress') -> logging.Logger: logging.Logger: Configured logger instance """ logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) # Create logs directory if it doesn't exist - log_dir = 'logs' - if not os.path.exists(log_dir): - os.makedirs(log_dir) + log_dir = os.path.join(os.getcwd(), 'logs') + try: + if not os.path.exists(log_dir): + os.makedirs(log_dir) + print(f"Created log directory at: {log_dir}") + except Exception as e: + print(f"Error creating log directory: {e}") + return logger # File handler with rotation - log_file = os.path.join(log_dir, f'{name}_{datetime.now().strftime("%Y%m%d")}.log') - file_handler = RotatingFileHandler( - log_file, - maxBytes=10*1024*1024, # 10MB - backupCount=5, - encoding='utf-8' - ) - file_handler.setLevel(logging.DEBUG) + try: + log_file = os.path.join(log_dir, f'{name}_{datetime.now().strftime("%Y%m%d")}.log') + file_handler = RotatingFileHandler( + log_file, + maxBytes=10*1024*1024, # 10MB + backupCount=5, + encoding='utf-8' + ) + file_handler.setLevel(logging.DEBUG) + + # Create formatters + file_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s' + ) + file_handler.setFormatter(file_formatter) + + # Add file handler to logger + logger.addHandler(file_handler) + print(f"Added file handler for: {log_file}") + except Exception as e: + print(f"Error setting up file handler: {e}") - # Console handler - console_handler = logging.StreamHandler(sys.stdout) + # Console handler - WARNING in production, INFO in other environments + try: + console_handler = logging.StreamHandler(sys.stdout) + if config.ENV_MODE == EnvMode.PRODUCTION: + console_handler.setLevel(logging.WARNING) + else: + console_handler.setLevel(logging.INFO) + + console_formatter = logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s' + ) + console_handler.setFormatter(console_formatter) + + # Add console handler to logger + logger.addHandler(console_handler) + print(f"Added console handler with level: {console_handler.level}") + except Exception as e: + print(f"Error setting up console handler: {e}") - if config.ENV_MODE == EnvMode.PRODUCTION: - console_handler.setLevel(logging.WARNING) - else: - console_handler.setLevel(logging.DEBUG) - - # Create formatters - file_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s' - ) - console_formatter = logging.Formatter( - '%(asctime)s - %(levelname)s - %(message)s' - ) - - # Set formatters - file_handler.setFormatter(file_formatter) - console_handler.setFormatter(console_formatter) - - # Add handlers to logger - logger.addHandler(file_handler) - logger.addHandler(console_handler) + # # Test logging + # logger.debug("Logger setup complete - DEBUG test") + # logger.info("Logger setup complete - INFO test") + # logger.warning("Logger setup complete - WARNING test") return logger