|
import logging |
|
import os |
|
import random |
|
import re |
|
import time |
|
from logging import LogRecord |
|
|
|
from colorama import Fore, Style |
|
|
|
from autogpt import speak |
|
from autogpt.config import Config, Singleton |
|
|
|
cfg = Config() |
|
|
|
""" |
|
Logger that handle titles in different colors. |
|
Outputs logs in console, activity.log, and errors.log |
|
For console handler: simulates typing |
|
""" |
|
|
|
|
|
class Logger(metaclass=Singleton): |
|
def __init__(self): |
|
|
|
this_files_dir_path = os.path.dirname(__file__) |
|
log_dir = os.path.join(this_files_dir_path, "../logs") |
|
if not os.path.exists(log_dir): |
|
os.makedirs(log_dir) |
|
|
|
log_file = "activity.log" |
|
error_file = "error.log" |
|
|
|
console_formatter = AutoGptFormatter("%(title_color)s %(message)s") |
|
|
|
|
|
self.typing_console_handler = TypingConsoleHandler() |
|
self.typing_console_handler.setLevel(logging.INFO) |
|
self.typing_console_handler.setFormatter(console_formatter) |
|
|
|
|
|
self.console_handler = ConsoleHandler() |
|
self.console_handler.setLevel(logging.DEBUG) |
|
self.console_handler.setFormatter(console_formatter) |
|
|
|
|
|
self.file_handler = logging.FileHandler(os.path.join(log_dir, log_file)) |
|
self.file_handler.setLevel(logging.DEBUG) |
|
info_formatter = AutoGptFormatter( |
|
"%(asctime)s %(levelname)s %(title)s %(message_no_color)s" |
|
) |
|
self.file_handler.setFormatter(info_formatter) |
|
|
|
|
|
error_handler = logging.FileHandler(os.path.join(log_dir, error_file)) |
|
error_handler.setLevel(logging.ERROR) |
|
error_formatter = AutoGptFormatter( |
|
"%(asctime)s %(levelname)s %(module)s:%(funcName)s:%(lineno)d %(title)s" |
|
" %(message_no_color)s" |
|
) |
|
error_handler.setFormatter(error_formatter) |
|
|
|
self.typing_logger = logging.getLogger("TYPER") |
|
self.typing_logger.addHandler(self.typing_console_handler) |
|
self.typing_logger.addHandler(self.file_handler) |
|
self.typing_logger.addHandler(error_handler) |
|
self.typing_logger.setLevel(logging.DEBUG) |
|
|
|
self.logger = logging.getLogger("LOGGER") |
|
self.logger.addHandler(self.console_handler) |
|
self.logger.addHandler(self.file_handler) |
|
self.logger.addHandler(error_handler) |
|
self.logger.setLevel(logging.DEBUG) |
|
|
|
def typewriter_log( |
|
self, title="", title_color="", content="", speak_text=False, level=logging.INFO |
|
): |
|
if speak_text and cfg.speak_mode: |
|
speak.say_text(f"{title}. {content}") |
|
|
|
if content: |
|
if isinstance(content, list): |
|
content = " ".join(content) |
|
else: |
|
content = "" |
|
|
|
self.typing_logger.log( |
|
level, content, extra={"title": title, "color": title_color} |
|
) |
|
|
|
def debug( |
|
self, |
|
message, |
|
title="", |
|
title_color="", |
|
): |
|
self._log(title, title_color, message, logging.DEBUG) |
|
|
|
def warn( |
|
self, |
|
message, |
|
title="", |
|
title_color="", |
|
): |
|
self._log(title, title_color, message, logging.WARN) |
|
|
|
def error(self, title, message=""): |
|
self._log(title, Fore.RED, message, logging.ERROR) |
|
|
|
def _log(self, title="", title_color="", message="", level=logging.INFO): |
|
if message: |
|
if isinstance(message, list): |
|
message = " ".join(message) |
|
self.logger.log(level, message, extra={"title": title, "color": title_color}) |
|
|
|
def set_level(self, level): |
|
self.logger.setLevel(level) |
|
self.typing_logger.setLevel(level) |
|
|
|
def double_check(self, additionalText=None): |
|
if not additionalText: |
|
additionalText = ( |
|
"Please ensure you've setup and configured everything" |
|
" correctly. Read https://github.com/Torantulino/Auto-GPT#readme to " |
|
"double check. You can also create a github issue or join the discord" |
|
" and ask there!" |
|
) |
|
|
|
self.typewriter_log("DOUBLE CHECK CONFIGURATION", Fore.YELLOW, additionalText) |
|
|
|
|
|
""" |
|
Output stream to console using simulated typing |
|
""" |
|
|
|
|
|
class TypingConsoleHandler(logging.StreamHandler): |
|
def emit(self, record): |
|
min_typing_speed = 0.05 |
|
max_typing_speed = 0.01 |
|
|
|
msg = self.format(record) |
|
try: |
|
words = msg.split() |
|
for i, word in enumerate(words): |
|
print(word, end="", flush=True) |
|
if i < len(words) - 1: |
|
print(" ", end="", flush=True) |
|
typing_speed = random.uniform(min_typing_speed, max_typing_speed) |
|
time.sleep(typing_speed) |
|
|
|
min_typing_speed = min_typing_speed * 0.95 |
|
max_typing_speed = max_typing_speed * 0.95 |
|
print() |
|
except Exception: |
|
self.handleError(record) |
|
|
|
|
|
class ConsoleHandler(logging.StreamHandler): |
|
def emit(self, record) -> None: |
|
msg = self.format(record) |
|
try: |
|
print(msg) |
|
except Exception: |
|
self.handleError(record) |
|
|
|
|
|
class AutoGptFormatter(logging.Formatter): |
|
""" |
|
Allows to handle custom placeholders 'title_color' and 'message_no_color'. |
|
To use this formatter, make sure to pass 'color', 'title' as log extras. |
|
""" |
|
|
|
def format(self, record: LogRecord) -> str: |
|
if hasattr(record, "color"): |
|
record.title_color = ( |
|
getattr(record, "color") |
|
+ getattr(record, "title") |
|
+ " " |
|
+ Style.RESET_ALL |
|
) |
|
else: |
|
record.title_color = getattr(record, "title") |
|
if hasattr(record, "msg"): |
|
record.message_no_color = remove_color_codes(getattr(record, "msg")) |
|
else: |
|
record.message_no_color = "" |
|
return super().format(record) |
|
|
|
|
|
def remove_color_codes(s: str) -> str: |
|
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") |
|
return ansi_escape.sub("", s) |
|
|
|
|
|
logger = Logger() |
|
|