diff --git a/web/agent/pyproject.toml b/web/agent/pyproject.toml index aa48c46..c79a823 100644 --- a/web/agent/pyproject.toml +++ b/web/agent/pyproject.toml @@ -13,4 +13,5 @@ dependencies = [ "python-dotenv", "httpx", "logfire>=4.16.0", + "python-json-logger>=4.0.0", ] diff --git a/web/agent/src/agent.py b/web/agent/src/agent.py index c8247c0..0d39771 100644 --- a/web/agent/src/agent.py +++ b/web/agent/src/agent.py @@ -7,10 +7,20 @@ import logging import httpx import logfire +# Set up logging BEFORE logfire (otherwise basicConfig is ignored) +from pythonjsonlogger import jsonlogger + +log_level = os.getenv("LOG_LEVEL", "INFO").upper() +handler = logging.StreamHandler() +handler.setFormatter(jsonlogger.JsonFormatter("%(asctime)s %(name)s %(levelname)s %(message)s")) +logging.basicConfig( + level=getattr(logging, log_level, logging.INFO), + handlers=[handler], +) +logger = logging.getLogger(__name__) + # Configure Logfire for observability -# Uses LOGFIRE_TOKEN in production, or local auth from `logfire auth` in dev logfire.configure( - project_name='cavepediav2', environment=os.getenv('ENVIRONMENT', 'development'), ) logfire.instrument_pydantic_ai() @@ -19,14 +29,6 @@ logfire.instrument_httpx() from pydantic_ai import Agent, ModelMessage, RunContext from pydantic_ai.settings import ModelSettings -# Set up logging based on environment -log_level = logging.DEBUG if os.getenv("DEBUG") else logging.INFO -logging.basicConfig( - level=log_level, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - CAVE_MCP_URL = os.getenv("CAVE_MCP_URL", "https://mcp.caving.dev/mcp") logger.info(f"Initializing Cavepedia agent with CAVE_MCP_URL={CAVE_MCP_URL}") diff --git a/web/agent/src/main.py b/web/agent/src/main.py index de95c98..3edbe44 100644 --- a/web/agent/src/main.py +++ b/web/agent/src/main.py @@ -14,13 +14,27 @@ from pydantic_ai.settings import ModelSettings load_dotenv() # Set up logging based on environment -log_level = logging.DEBUG if os.getenv("DEBUG") else logging.INFO +from pythonjsonlogger import jsonlogger + +log_level = os.getenv("LOG_LEVEL", "INFO").upper() +json_formatter = jsonlogger.JsonFormatter("%(asctime)s %(name)s %(levelname)s %(message)s") + +# Configure root logger with JSON +handler = logging.StreamHandler() +handler.setFormatter(json_formatter) logging.basicConfig( - level=log_level, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" + level=getattr(logging, log_level, logging.INFO), + handlers=[handler], ) logger = logging.getLogger(__name__) +# Apply JSON formatter to uvicorn loggers (works even when run via `uvicorn src.main:app`) +for uvicorn_logger_name in ("uvicorn", "uvicorn.error", "uvicorn.access"): + uvicorn_logger = logging.getLogger(uvicorn_logger_name) + uvicorn_logger.handlers = [handler] + uvicorn_logger.setLevel(getattr(logging, log_level, logging.INFO)) + uvicorn_logger.propagate = False + # Validate required environment variables if not os.getenv("ANTHROPIC_API_KEY"): logger.error("ANTHROPIC_API_KEY environment variable is required") @@ -41,12 +55,9 @@ logger.info("Creating AG-UI app...") async def handle_agent_request(request: Request) -> Response: """Handle incoming AG-UI requests with dynamic role-based MCP configuration.""" - # Debug: log all incoming headers - logger.info(f"DEBUG: All request headers: {dict(request.headers)}") # Extract user roles from request headers roles_header = request.headers.get("x-user-roles", "") - logger.info(f"DEBUG: x-user-roles header value: '{roles_header}'") user_roles = [] if roles_header: diff --git a/web/agent/uv.lock b/web/agent/uv.lock index 8ca536e..578deac 100644 --- a/web/agent/uv.lock +++ b/web/agent/uv.lock @@ -236,6 +236,7 @@ dependencies = [ { name = "openai" }, { name = "pydantic-ai" }, { name = "python-dotenv" }, + { name = "python-json-logger" }, { name = "starlette" }, { name = "uvicorn" }, ] @@ -249,6 +250,7 @@ requires-dist = [ { name = "openai" }, { name = "pydantic-ai" }, { name = "python-dotenv" }, + { name = "python-json-logger", specifier = ">=4.0.0" }, { name = "starlette" }, { name = "uvicorn" }, ]