add log mode and log level, support for logging to files in .ra-aid/logs/

This commit is contained in:
AI Christianson 2025-03-02 12:18:27 -05:00
parent f89d40527d
commit 09bd7cbf4b
5 changed files with 135 additions and 124 deletions

View File

@ -182,8 +182,11 @@ ra-aid -m "Your task or query here"
# Research-only mode (no implementation)
ra-aid -m "Explain the authentication flow" --research-only
# Enable verbose logging for detailed execution information
ra-aid -m "Add new feature" --verbose
# File logging with console warnings (default mode)
ra-aid -m "Add new feature" --log-mode file
# Console-only logging with detailed output
ra-aid -m "Add new feature" --log-mode console
```
More information is available in our [Usage Examples](https://docs.ra-aid.ai/category/usage).
@ -203,7 +206,10 @@ More information is available in our [Usage Examples](https://docs.ra-aid.ai/cat
- `--expert-model`: The model name to use for expert knowledge queries (required for non-OpenAI providers)
- `--hil, -H`: Enable human-in-the-loop mode for interactive assistance during task execution
- `--chat`: Enable chat mode with direct human interaction (implies --hil)
- `--verbose`: Enable verbose logging output
- `--log-mode`: Logging mode (choices: file, console)
- `file`: Logs to both file and console (only warnings+ to console)
- `console`: Logs to console only at the specified log level
- `--log-level`: Set specific logging level (debug, info, warning, error, critical)
- `--experimental-fallback-handler`: Enable experimental fallback handler to attempt to fix too calls when the same tool fails 3 times consecutively. (OPENAI_API_KEY recommended as openai has the top 5 tool calling models.) See `ra_aid/tool_leaderboard.py` for more info.
- `--pretty-logger`: Enables panel markdown formatted logger messages for debugging purposes.
- `--temperature`: LLM temperature (0.0-2.0) to control randomness in responses

View File

@ -1,58 +0,0 @@
#!/usr/bin/env python3
"""
Create initial database migration script.
This script creates a baseline migration representing the current database schema.
It serves as the foundation for future schema changes.
"""
import os
import sys
# Add the project root to the Python path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ra_aid.database import DatabaseManager, create_new_migration
from ra_aid.logging_config import get_logger, setup_logging
# Set up logging
setup_logging(verbose=True)
logger = get_logger(__name__)
def create_initial_migration():
"""
Create the initial migration for the current database schema.
Returns:
bool: True if migration was created successfully, False otherwise
"""
try:
with DatabaseManager() as db:
# Create a descriptive name for the initial migration
migration_name = "initial_schema"
# Create the migration
logger.info(f"Creating initial migration '{migration_name}'...")
result = create_new_migration(migration_name, auto=True)
if result:
logger.info(f"Successfully created initial migration: {result}")
print(f"✅ Initial migration created successfully: {result}")
return True
else:
logger.error("Failed to create initial migration")
print("❌ Failed to create initial migration")
return False
except Exception as e:
logger.error(f"Error creating initial migration: {str(e)}")
print(f"❌ Error creating initial migration: {str(e)}")
return False
if __name__ == "__main__":
print("Creating initial database migration...")
success = create_initial_migration()
# Exit with appropriate code
sys.exit(0 if success else 1)

View File

@ -1,9 +1,28 @@
import argparse
import logging
import os
import sys
import uuid
from datetime import datetime
# Add litellm import
import litellm
# Configure litellm to suppress debug logs
os.environ["LITELLM_LOG"] = "ERROR"
litellm.suppress_debug_info = True
litellm.set_verbose = False
# Explicitly configure LiteLLM's loggers
for logger_name in ["litellm", "LiteLLM"]:
litellm_logger = logging.getLogger(logger_name)
litellm_logger.setLevel(logging.WARNING)
litellm_logger.propagate = True
# Use litellm's internal method to disable debugging
if hasattr(litellm, "_logging") and hasattr(litellm._logging, "_disable_debugging"):
litellm._logging._disable_debugging()
from langgraph.checkpoint.memory import MemorySaver
from rich.console import Console
from rich.panel import Panel
@ -152,7 +171,8 @@ Examples:
help="Enable chat mode with direct human interaction (implies --hil)",
)
parser.add_argument(
"--verbose", action="store_true", help="Enable verbose logging output"
"--log-mode", choices=["console", "file"], default="file",
help="Logging mode: 'console' shows all logs in console, 'file' logs to file with only warnings+ in console"
)
parser.add_argument(
"--pretty-logger", action="store_true", help="Enable pretty logging output"
@ -161,7 +181,7 @@ Examples:
"--log-level",
type=log_level_type,
default="warning",
help="Set specific logging level (case-insensitive, overrides --verbose)",
help="Set specific logging level (case-insensitive, affects file and console logging based on --log-mode)",
)
parser.add_argument(
"--temperature",
@ -348,7 +368,7 @@ def build_status(
def main():
"""Main entry point for the ra-aid command line tool."""
args = parse_arguments()
setup_logging(args.verbose, args.pretty_logger, args.log_level)
setup_logging(args.log_mode, args.pretty_logger, args.log_level)
logger.debug("Starting RA.Aid with arguments: %s", args)
# Launch web interface if requested
@ -598,4 +618,4 @@ def main():
if __name__ == "__main__":
main()
main()

View File

@ -41,49 +41,74 @@ class PrettyHandler(logging.Handler):
self.handleError(record)
def setup_logging(verbose: bool = False, pretty: bool = False, log_level: Optional[str] = None) -> None:
def setup_logging(log_mode: str = "file", pretty: bool = False, log_level: Optional[str] = None) -> None:
"""
Configure logging for ra_aid.
Args:
verbose: Set to True to enable verbose logging (implies DEBUG level if log_level not specified)
pretty: Set to True to enable pretty console logging
log_level: Optional explicit log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
Takes precedence over verbose flag if specified
log_mode: Determines where logs are output. Options:
- "file": Log to both file and console, with console showing only warnings+
- "console": Log to console at the specified log_level (no file logging)
pretty: Set to True to enable pretty console logging.
log_level: Optional explicit log level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
When log_mode="file": Only affects file logging level, console shows warnings+
When log_mode="console": Controls the console logging level
Console logging behavior:
- With log_mode="file": Only WARNING and higher level messages are shown in console
- With log_mode="console": Console shows messages at the requested log_level
File logging behavior:
- Only active when log_mode="file"
- Uses the requested log_level
- When log_level=debug is used with log_mode="file", debug logs only go to the file, not to the console
"""
# Create .ra-aid/logs directory if it doesn't exist
cwd = os.getcwd()
ra_aid_dir_str = os.path.join(cwd, ".ra-aid")
logs_dir_str = os.path.join(ra_aid_dir_str, "logs")
# Create directory structure
for directory in [ra_aid_dir_str, logs_dir_str]:
path = Path(directory)
if not path.exists():
try:
path.mkdir(mode=0o755, parents=True, exist_ok=True)
except Exception as e:
print(f"Warning: Failed to create log directory {directory}: {str(e)}")
# Create directory structure if log_mode is "file"
if log_mode == "file":
for directory in [ra_aid_dir_str, logs_dir_str]:
path = Path(directory)
if not path.exists():
try:
path.mkdir(mode=0o755, parents=True, exist_ok=True)
except Exception as e:
print(f"Warning: Failed to create log directory {directory}: {str(e)}")
# Determine log level
if log_level is not None:
# Use provided log level if specified (case-insensitive)
numeric_level = getattr(logging, log_level.upper(), None)
if not isinstance(numeric_level, int):
specified_log_level = getattr(logging, log_level.upper(), None)
if not isinstance(specified_log_level, int):
# If invalid log level is provided, fall back to default
print(f"Invalid log level: {log_level}")
numeric_level = logging.DEBUG if verbose else logging.WARNING
specified_log_level = logging.WARNING
else:
# Use verbose flag to determine log level
numeric_level = logging.DEBUG if verbose else logging.WARNING
# No log_level specified, use WARNING as default
specified_log_level = logging.WARNING
# Configure root logger
logger = logging.getLogger("ra_aid")
logger.setLevel(numeric_level)
# Determine console log level based on log_mode
if log_mode == "console":
# When log_mode="console", use the specified log level for console
console_log_level = specified_log_level
else:
# When log_mode="file", console only shows warnings and errors
console_log_level = logging.WARNING
# Clear existing handlers to avoid duplicates
if logger.handlers:
logger.handlers.clear()
# Configure the root logger
root_logger = logging.getLogger()
# Always set the root logger to DEBUG level
# This ensures all messages flow through to their respective handlers
# Best practice is to set root logger to lowest level and let handlers control message filtering
root_logger.setLevel(logging.DEBUG)
# Clear existing handlers from root logger to avoid duplicates
if root_logger.handlers:
root_logger.handlers.clear()
# Create console handler
if pretty:
@ -95,34 +120,50 @@ def setup_logging(verbose: bool = False, pretty: bool = False, log_level: Option
)
console_handler.setFormatter(formatter)
# Add console handler to logger
logger.addHandler(console_handler)
# Set console handler log level based on log_mode and log_level
console_handler.setLevel(console_log_level)
# Create file handler with rotation
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_filename = os.path.join(logs_dir_str, f"ra_aid_{timestamp}.log")
# RotatingFileHandler with 5MB max size and 100 backup files
file_handler = RotatingFileHandler(
log_filename,
maxBytes=5 * 1024 * 1024, # 5MB
backupCount=100,
encoding="utf-8"
)
file_formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
file_handler.setFormatter(file_formatter)
file_handler.setLevel(numeric_level)
# Add file handler to logger
logger.addHandler(file_handler)
logger.info(f"Log file created: {log_filename}")
except Exception as e:
logger.error(f"Failed to set up file logging: {str(e)}")
# Add console handler to root logger
root_logger.addHandler(console_handler)
# Create file handler with rotation - only when log_mode is "file"
if log_mode == "file":
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_filename = os.path.join(logs_dir_str, f"ra_aid_{timestamp}.log")
# RotatingFileHandler with 5MB max size and 100 backup files
file_handler = RotatingFileHandler(
log_filename,
maxBytes=5 * 1024 * 1024, # 5MB
backupCount=100,
encoding="utf-8"
)
file_formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
file_handler.setFormatter(file_formatter)
# File handler always uses the specified log level
file_handler.setLevel(specified_log_level)
# Add file handler to root logger
root_logger.addHandler(file_handler)
# Create an ra_aid logger for compatibility
logger = logging.getLogger("ra_aid")
logger.setLevel(logging.DEBUG)
logger.propagate = True # Let messages propagate to root handlers
# Log configuration details for debugging
logger.debug(f"Logging configuration: log_mode={log_mode}, log_level={log_level}, "
f"root_level={root_logger.level}, logger_level={logger.level}, "
f"console_level={console_handler.level}, file_level={file_handler.level}, "
f"propagate={logger.propagate}")
logger.info(f"Log file created: {log_filename}")
except Exception as e:
root_logger.error(f"Failed to set up file logging: {str(e)}")
def get_logger(name: Optional[str] = None) -> logging.Logger:

View File

@ -9,15 +9,17 @@ import traceback
from pathlib import Path
from typing import List
# Configure logging
logging.basicConfig(
level=logging.WARNING,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.StreamHandler(sys.__stderr__) # Use the real stderr
],
)
# Configure module-specific logging without affecting root logger
logger = logging.getLogger(__name__)
# Only configure this specific logger, not the root logger
if not logger.handlers: # Avoid adding handlers multiple times
logger.setLevel(logging.WARNING)
handler = logging.StreamHandler(sys.__stderr__) # Use the real stderr
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
# Prevent propagation to avoid affecting the root logger configuration
logger.propagate = False
# Add project root to Python path
project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))