forked from SylphAI-Inc/AdalFlow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogger.py
218 lines (170 loc) · 7.55 KB
/
logger.py
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
"""
This logger file provides easy configurability of the root and named loggers, along with a color print function for console output.
NOTE: If you are on Windows versions prior to Windows 10, the Command Prompt and PowerShell do not support ANSI color codes. You would need to use third-party libraries like colorama to enable ANSI color support.
Please add the following colorama setting at the beginning of your code:
Installation:
.. code-block:: bash
pip install colorama
Initialization:
.. code-block:: python
import colorama
colorama.init(autoreset=True)
"""
import logging
import sys
from typing import List, Tuple, Optional, Literal
import inspect
import os
from datetime import datetime
from adalflow.utils.global_config import get_adalflow_default_root_path
LOG_LEVELS = {
"DEBUG": logging.DEBUG,
"INFO": logging.INFO,
"WARNING": logging.WARNING,
"ERROR": logging.ERROR,
"CRITICAL": logging.CRITICAL,
}
# color map, from color string to colorama.Fore colors, easier to use, reduce friction
COLOR_MAP = {
"black": "\x1b[30m",
"blue": "\x1b[34m",
"cyan": "\x1b[36m",
"green": "\x1b[32m",
"magenta": "\x1b[35m",
"red": "\x1b[31m",
"white": "\x1b[37m",
"yellow": "\x1b[33m",
}
log = logging.getLogger(__name__)
def _get_log_config(
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
filepath: str = "./logs/app.log",
enable_console: bool = True,
enable_file: bool = True,
) -> Tuple[int, List[logging.Handler]]:
r"""Helper function to get the default log configuration.
We config logging with the following default settings:
1. Enable both console and file output.
2. Set up the default format: "%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d:%(funcName)s] - %(message)s".
The format is: time, log level, filename(where the log is from), line number, function name, message.
3. Set up the default date format: "%Y-%m-%d %H:%M:%S".
Args:
level (str): Log level. Defaults to "INFO". Options: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL".
filepath (str): Name of the output log file. Defaults to "./logs/app.log".
enable_console (bool): Control the console output. Defaults to True.
enable_file (bool): Control the file output. Defaults to True.
Returns:
Tuple[int, List[logging.Handler]]: The logging level and the list of handlers.
"""
def get_level(level: str) -> int:
"""Return the logging level constant based on a string."""
return LOG_LEVELS.get(level.upper(), logging.INFO)
# 2. Config the default format and style
format = "%(asctime)s - %(module)s - %(levelname)s - [%(filename)s:%(lineno)d:%(funcName)s] - %(message)s"
formatter = logging.Formatter(format, datefmt="%Y-%m-%d %H:%M:%S")
# Set up the handler
handlers: List[logging.Handler] = []
if enable_console:
handlers.append(logging.StreamHandler(sys.stdout))
if enable_file:
file_path: str = os.path.dirname(filepath)
os.makedirs(file_path, exist_ok=True)
handler = logging.FileHandler(filename=filepath, mode="a")
handlers.append(handler)
for h in handlers:
h.setFormatter(formatter)
return get_level(level), handlers
def get_logger(
name: Optional[str] = None,
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
save_dir: Optional[str] = None,
filename: Optional[str] = None,
enable_console: bool = True,
enable_file: bool = True,
) -> logging.Logger:
"""Get or configure a logger, including both the root logger and named logger.
Args:
name (Optional[str]): Name of the logger. Defaults to None which means root logger.
level (str): Log level. Defaults to "INFO". Options: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL".
save_dir (Optional[str]): Directory to save log files. Defaults to "./logs".
filename (Optional[str]): Name of the output log file. Defaults to "lib.log" for root logger and "{name}.log" for named logger.
enable_console (bool): Control the console output. Defaults to True.
enable_file (bool): Control the file output. Defaults to True.
Returns:
logging.Logger: The logger with the specified configuration.
Example:
1. Get the root logger and have it log to both console and file (~/.adalflow/logs/lib.log):
.. code-block:: python
from adalflow.utils.logger import get_logger
root_logger = get_logger(enable_console=True, enable_file=True)
2. Get a named logger and have it log to both console and file (./logs/app.log):
.. code-block:: python
logger = get_logger(name="app", enable_console=True, enable_file=True)
3. Use two loggers, one for the library and one for the application:
.. code-block:: python
root_logger = enable_library_logging(level="DEBUG", enable_console=True, enable_file=True)
logger = get_logger(name="app", level="DEBUG", enable_console=True, enable_file=True)
logger.info("This is an application log, and it will be saved in app.log separately from the library log at lib.log.")
"""
# save_dir = save_dir or "./logs"
save_dir = save_dir or get_adalflow_default_root_path() + "/logs"
os.makedirs(save_dir, exist_ok=True)
if name is None:
filename = filename or "lib.log"
else:
filename = filename or f"{name}.log"
filepath = os.path.join(save_dir, filename)
config = _get_log_config(
level=level,
filepath=filepath,
enable_console=enable_console,
enable_file=enable_file,
)
logger = logging.getLogger(name)
logger.setLevel(config[0])
logger.handlers = [] # Clear existing handlers
for handler in config[1]:
logger.addHandler(handler)
if name:
logger.propagate = False # Do not propagate to the root logger
return logger
def get_current_script_and_line() -> Tuple[str, str, int]:
"""Get the function name, script name, and line of where the print is called.
Returns:
function_name (str): the name of the function that is calling.
script_name (str): the name of the script that is calling.
line_number (str): the line number of the code that is calling.
"""
caller_frame = inspect.stack()[2]
file_path, line_number, function_name = (
caller_frame.filename,
caller_frame.lineno,
caller_frame.function,
)
script_name = os.path.basename(file_path)
return function_name, script_name, line_number
def printc(
text: str,
color: Literal[
"black", "blue", "cyan", "green", "magenta", "red", "white", "yellow"
] = "cyan",
):
"""Color enhanced print function with logger-like format.
LightRAG's customized print with colored text, position of code block the print is set, and current timestamp.
Args:
text (str): Text to be printed.
color (str): Color of the text. Defaults to "cyan". Options:
'black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'. Defaults to "cyan".
Example:
.. code-block:: python
from utils.logger import colored_print
printc("hello", color="green")
printc("hello")
"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
function_name, script_name, line_number = get_current_script_and_line()
color_code = COLOR_MAP.get(color, COLOR_MAP["cyan"])
print(
f"{color_code}{timestamp} - [{script_name}:{line_number}:{function_name}] - {text}\033[0m"
)
# \033[0m means reset, not impacting the next print texts