1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
"""
Centralized logging module for the experiment framework.
Provides configurable debug levels:
- Level 1: CRITICAL only
- Level 2: WARNING + CRITICAL
- Level 3: INFO + WARNING + CRITICAL
- Level 4: DEBUG + INFO + WARNING + CRITICAL (full API calls, reasoning, VFS)
"""
import logging
import os
import sys
from typing import Optional
# Custom level names
LEVEL_NAMES = {
logging.CRITICAL: "CRITICAL",
logging.WARNING: "WARN",
logging.INFO: "INFO",
logging.DEBUG: "DEBUG",
}
# Define DEBUG level (10) below INFO (20)
DEBUG_LEVEL = logging.DEBUG # 10
def setup_logger(
name: str = "experiment",
level: int = 3,
log_format: str = "[{level}] {message}",
output: str = "console",
log_file: Optional[str] = None
) -> logging.Logger:
"""
Set up and configure a logger.
Args:
name: Logger name
level: Debug level (1-4)
log_format: Format string for log messages
output: Output destination - "console", "file", or "both"
log_file: Path to log file (required if output is "file" or "both")
Returns:
Configured logger instance
"""
logger = logging.getLogger(name)
# Clear any existing handlers
logger.handlers.clear()
# Determine actual logging level based on debug level
# Level 1: CRITICAL only (50)
# Level 2: WARNING (30) + CRITICAL (50)
# Level 3: INFO (20) + WARNING (30) + CRITICAL (50)
# Level 4: DEBUG (10) + INFO + WARNING + CRITICAL (full API calls, reasoning, VFS)
if level >= 4:
actual_level = logging.DEBUG
elif level >= 3:
actual_level = logging.INFO
elif level >= 2:
actual_level = logging.WARNING
else:
actual_level = logging.CRITICAL
logger.setLevel(actual_level)
# Create formatter
class CustomFormatter(logging.Formatter):
"""Custom formatter that uses our format string."""
def format(self, record):
# Use our custom level names
levelname = record.levelname
if record.levelno == logging.CRITICAL:
levelname = "CRITICAL"
elif record.levelno == logging.WARNING:
levelname = "WARN"
elif record.levelno == logging.INFO:
levelname = "INFO"
elif record.levelno == logging.DEBUG:
levelname = "DEBUG"
msg = record.getMessage()
return log_format.format(level=levelname, message=msg)
formatter = CustomFormatter()
# For level 4 (DEBUG), automatically enable both console and file output
if level >= 4 and output == "console":
output = "both"
if not log_file:
log_file = "logs/experiment.log"
# Console handler
if output in ("console", "both"):
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# File handler
if output in ("file", "both"):
if not log_file:
raise ValueError("log_file must be specified when output is 'file' or 'both'")
# Ensure directory exists
log_dir = os.path.dirname(log_file)
if log_dir:
os.makedirs(log_dir, exist_ok=True)
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
def get_logger(name: str = "experiment") -> logging.Logger:
"""
Get an existing logger by name.
Args:
name: Logger name
Returns:
Logger instance
"""
return logging.getLogger(name)
|