Version: 2.0.0-alpha.6 Added: Phase 2 completion Purpose: Structured logging with console and file output
The AI CLI Preparation tool now includes a comprehensive logging framework that replaces the previous print-based vlog() system with structured Python logging. This provides better control over log output, supports multiple output destinations, and enables production-grade debugging capabilities.
- Multiple log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
- Console output with colors: Visual distinction between log levels
- File logging: Persistent logs for production debugging
- Backward compatible: Existing
vlog()calls automatically use new system - Configurable: Control verbosity, output destinations, and formatting
- Testing support: Full integration with pytest's caplog fixture
from cli_audit import setup_logging, get_logger
# Setup logging
setup_logging(verbose=True)
# Get logger instance
logger = get_logger()
# Log messages
logger.info("Starting operation")
logger.warning("Potential issue detected")
logger.error("Operation failed")from cli_audit.logging_config import info, warning, error
# Simple logging with verbose flag
info("Processing complete", verbose=True)
warning("Deprecated feature used", verbose=True)
error("Configuration invalid", verbose=True)from cli_audit.common import vlog
# Existing code continues to work
vlog("Detecting installations...", verbose=True)
# Now uses structured logging internallydef setup_logging(
level: str = "INFO",
log_file: Optional[str] = None,
verbose: bool = False,
quiet: bool = False,
propagate: bool = False,
) -> logging.LoggerParameters:
-
level (
str): Base log level - "DEBUG", "INFO", "WARNING", "ERROR", or "CRITICAL"- Default: "INFO"
- Example:
setup_logging(level="DEBUG")
-
log_file (
str | None): Path to log file for persistent logging- Default: None (console only)
- Creates parent directories if needed
- Example:
setup_logging(log_file="~/.cli-audit/logs/audit.log")
-
verbose (
bool): Enable verbose (DEBUG) output- Default: False
- Overrides level parameter when True
- Example:
setup_logging(verbose=True)
-
quiet (
bool): Suppress console output (file logging only)- Default: False
- Useful for background processes
- Example:
setup_logging(quiet=True, log_file="/var/log/cli-audit.log")
-
propagate (
bool): Allow log propagation to root logger- Default: False
- Set to True for pytest integration
- Example:
setup_logging(propagate=True) # For testing
Returns: Configured logging.Logger instance
From most to least verbose:
-
DEBUG: Detailed diagnostic information
- File operations, API calls, algorithm steps
- Example: "Checking PATH directory: /home/user/.cargo/bin"
-
INFO: General informational messages
- Operation progress, successful completions
- Example: "Detected 3 installations of ripgrep"
-
WARNING: Potential issues that don't prevent operation
- Configuration warnings, deprecations
- Example: "PATH ordering issue: /usr/bin before ~/.cargo/bin"
-
ERROR: Operation failures that need attention
- Installation failures, network errors
- Example: "Failed to install tool: network timeout"
-
CRITICAL: Severe errors requiring immediate action
- System-level failures, data corruption
- Example: "Cannot access package manager: permission denied"
Log levels are visually distinguished with colors and symbols:
🔍 DEBUG: Cyan text
✓ INFO: Green text
⚠️ WARNING: Yellow text
✗ ERROR: Red text
🚨 CRITICAL: Bold red text
from cli_audit import setup_logging, get_logger
# Setup with default INFO level
setup_logging()
logger = get_logger()
logger.info("Application started")
logger.warning("Using default configuration")
logger.error("Configuration file not found")Output:
✓ INFO Application started
⚠️ WARNING Using default configuration
✗ ERROR Configuration file not found
from cli_audit import setup_logging, get_logger
# Enable verbose (DEBUG) output
setup_logging(verbose=True)
logger = get_logger()
logger.debug("Scanning PATH directories")
logger.debug("Found candidate: /home/user/.cargo/bin/rg")
logger.info("Detection complete")Output:
🔍 DEBUG Scanning PATH directories
🔍 DEBUG Found candidate: /home/user/.cargo/bin/rg
✓ INFO Detection complete
from cli_audit import setup_logging, get_logger
# Log to file with auto-directory creation
setup_logging(
level="INFO",
log_file="~/.cli-audit/logs/production.log"
)
logger = get_logger()
logger.info("Production deployment started")
logger.error("Failed to connect to package registry")File content (~/.cli-audit/logs/production.log):
2025-10-09 14:23:45 [INFO] cli_audit: Production deployment started
2025-10-09 14:23:46 [ERROR] cli_audit: Failed to connect to package registry
from cli_audit import setup_logging, get_logger
# Suppress console, log to file only
setup_logging(
quiet=True,
log_file="/var/log/cli-audit-daemon.log"
)
logger = get_logger()
logger.info("Daemon started") # Only in file, not consolefrom cli_audit.logging_config import info, warning, error, debug
# Simple logging with verbose control
debug("Detailed diagnostic info", verbose=True)
info("Operation progress update", verbose=True)
warning("Non-critical issue detected", verbose=True)
error("Operation failed", verbose=True)from cli_audit.common import vlog
# Existing code works without modification
vlog("Detecting installations...", verbose=True)
vlog("Found 3 installations", verbose=True)
# Internally uses new logging system
# Output: ✓ INFO Detecting installations...
# ✓ INFO Found 3 installationsAll existing modules automatically use the new logging system through vlog():
# cli_audit/reconcile.py
from .common import vlog
def detect_installations(tool_name: str, verbose: bool = False):
vlog(f"Detecting installations of {tool_name}...", verbose)
# vlog now uses structured logging internallyExisting code using vlog() continues to work:
- ✅
cli_audit/environment.py- 12 vlog calls - ✅
cli_audit/config.py- 8 vlog calls - ✅
cli_audit/installer.py- 23 vlog calls - ✅
cli_audit/bulk.py- 15 vlog calls - ✅
cli_audit/upgrade.py- 18 vlog calls - ✅
cli_audit/reconcile.py- 34 vlog calls
Total: 110+ vlog calls automatically upgraded to structured logging
The logging framework includes 19 comprehensive tests:
pytest tests/test_logging.py -vTest Coverage:
- ✅ Setup with different levels (DEBUG, INFO, WARNING, etc.)
- ✅ Verbose and quiet modes
- ✅ File logging with directory creation
- ✅ Colored formatter with/without colors
- ✅ Convenience functions (debug, info, warning, error, critical)
- ✅ Backward compatibility with vlog()
For testing code that uses logging:
def test_my_function(caplog):
"""Test function with logging."""
from cli_audit import setup_logging, get_logger
# Enable propagate for pytest integration
setup_logging(level="INFO", propagate=True)
# Test your code
with caplog.at_level(logging.INFO, logger="cli_audit"):
my_function()
assert "Expected message" in caplog.textControl logging via environment variables:
# Enable debug output
export CLI_AUDIT_DEBUG=1
# Specify log file
export CLI_AUDIT_LOG_FILE=~/.cli-audit/debug.log
# Set log level
export CLI_AUDIT_LOG_LEVEL=WARNINGPlanned CLI integration:
# Verbose output
cli_audit audit --verbose
# Quiet mode
cli_audit install --quiet --log-file /tmp/install.log
# Debug level
cli_audit reconcile --log-level DEBUG# DEBUG: Detailed diagnostic information
logger.debug(f"Checking PATH directory: {path_dir}")
# INFO: Successful operations and progress
logger.info(f"Detected {count} installations")
# WARNING: Potential issues
logger.warning(f"PATH ordering issue: {issue}")
# ERROR: Operation failures
logger.error(f"Failed to install {tool}: {error}")
# CRITICAL: System-level failures
logger.critical(f"Cannot access package manager")# Development: Console only
setup_logging(verbose=True)
# Production: Console + File
setup_logging(
level="INFO",
log_file="/var/log/cli-audit.log"
)
# Background daemon: File only
setup_logging(
quiet=True,
log_file="/var/log/cli-audit-daemon.log"
)# Good: Includes context
logger.error(f"Failed to install {tool_name} via {method}: {error_message}")
# Bad: Missing context
logger.error("Installation failed")# Log structured data
logger.info(
f"Installation complete: tool={tool_name}, "
f"version={version}, method={method}, duration={duration}s"
)try:
result = install_tool(tool_name)
except Exception as e:
logger.error(f"Installation failed: {e}", exc_info=True)
# exc_info=True includes stack trace in file logsLogging framework only evaluates log messages at or above the configured level:
# With level=INFO
logger.debug("Expensive operation") # ← Never evaluated (fast)
logger.info("Regular operation") # ← Evaluated and loggedFile logging is buffered and asynchronous-safe:
- Console output: Immediate (for user feedback)
- File output: Buffered (for performance)
- Logger instance: ~1KB (singleton)
- Each handler: ~2KB
- Log records: Garbage collected after processing
Symptom: No console output despite logging calls
Solution:
# Check log level
logger = get_logger()
print(logger.level) # Should be <= logging.INFO
# Ensure verbose=True for vlog
vlog("Message", verbose=True) # Not False!
# Verify logger is configured
from cli_audit import setup_logging
setup_logging(verbose=True) # Call before loggingSymptom: Log file not created despite log_file parameter
Solution:
# Check file path
import os
log_path = os.path.expanduser("~/.cli-audit/logs/audit.log")
print(f"Log file: {log_path}")
# Verify permissions
print(f"Can write: {os.access(os.path.dirname(log_path), os.W_OK)}")
# Create directory manually if needed
os.makedirs(os.path.dirname(log_path), exist_ok=True)Symptom: ANSI color codes visible instead of colored text
Solution:
# Colors require TTY
import sys
print(f"TTY: {sys.stdout.isatty()}") # Should be True
# Force colors in ColoredFormatter
formatter = ColoredFormatter(fmt, use_colors=True)Symptom: Each log message appears multiple times
Solution:
# Clear handlers before setup
logger = logging.getLogger("cli_audit")
logger.handlers.clear() # Remove old handlers
# Then setup fresh
setup_logging(verbose=True)Old Code:
from cli_audit.common import vlog
def my_function(verbose=False):
vlog("Starting operation", verbose)
# ... code ...
vlog("Operation complete", verbose)New Code (Option 1 - No Changes):
from cli_audit.common import vlog
# Existing code works unchanged
def my_function(verbose=False):
vlog("Starting operation", verbose)
vlog("Operation complete", verbose)
# vlog now uses structured logging internallyNew Code (Option 2 - Direct Logging):
from cli_audit import get_logger
def my_function():
logger = get_logger()
logger.info("Starting operation")
# ... code ...
logger.info("Operation complete")Old Code:
def install_tool(tool_name):
print(f"Installing {tool_name}...")
# ... code ...
print(f"✓ Installed {tool_name}")New Code:
from cli_audit import get_logger
def install_tool(tool_name):
logger = get_logger()
logger.info(f"Installing {tool_name}...")
# ... code ...
logger.info(f"Installed {tool_name}")def setup_logging(
level: str = "INFO",
log_file: Optional[str] = None,
verbose: bool = False,
quiet: bool = False,
propagate: bool = False,
) -> logging.LoggerConfigure logging for the application. See Configuration section for details.
def get_logger() -> logging.LoggerGet the configured logger instance. Initializes with defaults if not yet set up.
def debug(msg: str, verbose: bool = True) -> NoneLog debug message (only if verbose=True).
def info(msg: str, verbose: bool = True) -> NoneLog info message.
def warning(msg: str, verbose: bool = True) -> NoneLog warning message.
def error(msg: str, verbose: bool = True) -> NoneLog error message.
def critical(msg: str, verbose: bool = True) -> NoneLog critical message.
class ColoredFormatter(logging.Formatter):
"""Formatter with colored output for different log levels."""
def __init__(self, fmt: str, use_colors: bool = True):
"""Initialize with format string and color flag."""Color Mapping:
- DEBUG: Cyan (🔍)
- INFO: Green (✓)
- WARNING: Yellow (
⚠️ ) - ERROR: Red (✗)
- CRITICAL: Bold Red (🚨)
Added:
- Structured logging framework with Python
loggingmodule - Console output with colored formatting
- File logging with automatic directory creation
- Multiple log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- Backward compatible
vlog()integration - Convenience functions:
debug(),info(),warning(),error(),critical() - 19 comprehensive unit tests
- Full pytest integration with caplog support
Changed:
vlog()now uses structured logging internally (backward compatible)- All print-based logging converted to proper log levels
Fixed:
- Log output now respects configured levels
- File logging creates parent directories automatically
- Colors disabled for non-TTY output (e.g., piped output)
-
Structured Log Fields
logger.info("Installation complete", extra={ "tool": tool_name, "version": version, "duration": duration })
-
JSON Log Format
setup_logging(format="json", log_file="audit.jsonl") # Output: {"timestamp":"2025-10-09T14:23:45","level":"INFO","message":"..."}
-
Log Rotation
setup_logging( log_file="audit.log", max_bytes=10*1024*1024, # 10MB backup_count=5 )
-
Remote Logging
setup_logging( syslog_host="logs.example.com", syslog_port=514 )
-
Performance Metrics
logger.metric("install_duration", duration, tool=tool_name)
Document Version: 1.0 Last Updated: 2025-10-09 Test Coverage: 19 tests, 100% passing Integration Status: ✅ Fully integrated with Phases 2.1-2.5