diff options
Diffstat (limited to 'autogpts/autogpt/autogpt/logs')
-rw-r--r-- | autogpts/autogpt/autogpt/logs/__init__.py | 15 | ||||
-rw-r--r-- | autogpts/autogpt/autogpt/logs/config.py | 156 | ||||
-rw-r--r-- | autogpts/autogpt/autogpt/logs/formatters.py | 16 | ||||
-rw-r--r-- | autogpts/autogpt/autogpt/logs/helpers.py | 7 | ||||
-rw-r--r-- | autogpts/autogpt/autogpt/logs/utils.py | 4 |
5 files changed, 147 insertions, 51 deletions
diff --git a/autogpts/autogpt/autogpt/logs/__init__.py b/autogpts/autogpt/autogpt/logs/__init__.py index abcdac713..dc99649e5 100644 --- a/autogpts/autogpt/autogpt/logs/__init__.py +++ b/autogpts/autogpt/autogpt/logs/__init__.py @@ -1,3 +1,4 @@ +from .config import configure_chat_plugins, configure_logging from .helpers import user_friendly_output from .log_cycle import ( CURRENT_CONTEXT_FILE_NAME, @@ -9,3 +10,17 @@ from .log_cycle import ( USER_INPUT_FILE_NAME, LogCycleHandler, ) + +__all__ = [ + "configure_logging", + "configure_chat_plugins", + "user_friendly_output", + "CURRENT_CONTEXT_FILE_NAME", + "NEXT_ACTION_FILE_NAME", + "PROMPT_SUMMARY_FILE_NAME", + "PROMPT_SUPERVISOR_FEEDBACK_FILE_NAME", + "SUMMARY_FILE_NAME", + "SUPERVISOR_FEEDBACK_FILE_NAME", + "USER_INPUT_FILE_NAME", + "LogCycleHandler", +] diff --git a/autogpts/autogpt/autogpt/logs/config.py b/autogpts/autogpt/autogpt/logs/config.py index 7b8a043b1..ae483d0f8 100644 --- a/autogpts/autogpt/autogpt/logs/config.py +++ b/autogpts/autogpt/autogpt/logs/config.py @@ -1,21 +1,24 @@ """Logging module for Auto-GPT.""" from __future__ import annotations +import enum import logging +import os import sys from pathlib import Path from typing import TYPE_CHECKING, Optional from auto_gpt_plugin_template import AutoGPTPluginTemplate -from openai.util import logger as openai_logger +from openai._base_client import log as openai_logger if TYPE_CHECKING: from autogpt.config import Config from autogpt.speech import TTSConfig +from autogpt.core.configuration import SystemConfiguration, UserConfigurable from autogpt.core.runner.client_lib.logging import BelowLevelFilter -from .formatters import AutoGptFormatter +from .formatters import AutoGptFormatter, StructuredLoggingFormatter from .handlers import TTSHandler, TypingConsoleHandler LOG_DIR = Path(__file__).parent.parent.parent / "logs" @@ -34,80 +37,141 @@ USER_FRIENDLY_OUTPUT_LOGGER = "USER_FRIENDLY_OUTPUT" _chat_plugins: list[AutoGPTPluginTemplate] = [] +class LogFormatName(str, enum.Enum): + SIMPLE = "simple" + DEBUG = "debug" + STRUCTURED = "structured_google_cloud" + + +TEXT_LOG_FORMAT_MAP = { + LogFormatName.DEBUG: DEBUG_LOG_FORMAT, + LogFormatName.SIMPLE: SIMPLE_LOG_FORMAT, +} + + +class LoggingConfig(SystemConfiguration): + level: int = UserConfigurable( + default=logging.INFO, + from_env=lambda: logging.getLevelName(os.getenv("LOG_LEVEL", "INFO")), + ) + + # Console output + log_format: LogFormatName = UserConfigurable( + default=LogFormatName.SIMPLE, + from_env=lambda: LogFormatName(os.getenv("LOG_FORMAT", "simple")), + ) + plain_console_output: bool = UserConfigurable( + default=False, + from_env=lambda: os.getenv("PLAIN_OUTPUT", "False") == "True", + ) + + # File output + log_dir: Path = LOG_DIR + log_file_format: Optional[LogFormatName] = UserConfigurable( + default=LogFormatName.SIMPLE, + from_env=lambda: LogFormatName( + os.getenv("LOG_FILE_FORMAT", os.getenv("LOG_FORMAT", "simple")) + ), + ) + + def configure_logging( - debug_mode: bool = False, - plain_output: bool = False, - tts_config: Optional[TTSConfig] = None, + level: int = logging.INFO, log_dir: Path = LOG_DIR, + log_format: Optional[LogFormatName] = None, + log_file_format: Optional[LogFormatName] = None, + plain_console_output: bool = False, + tts_config: Optional[TTSConfig] = None, ) -> None: - """Configure the native logging module.""" + """Configure the native logging module. + + Should be usable as `configure_logging(**config.logging.dict())`, where + `config.logging` is a `LoggingConfig` object. + """ + + # Auto-adjust default log format based on log level + log_format = log_format or ( + LogFormatName.SIMPLE if level != logging.DEBUG else LogFormatName.DEBUG + ) + log_file_format = log_file_format or log_format + + structured_logging = log_format == LogFormatName.STRUCTURED + + if structured_logging: + plain_console_output = True + log_file_format = None # create log directory if it doesn't exist if not log_dir.exists(): log_dir.mkdir() - log_level = logging.DEBUG if debug_mode else logging.INFO - log_format = DEBUG_LOG_FORMAT if debug_mode else SIMPLE_LOG_FORMAT - console_formatter = AutoGptFormatter(log_format) + log_handlers: list[logging.Handler] = [] + + if log_format in (LogFormatName.DEBUG, LogFormatName.SIMPLE): + console_format_template = TEXT_LOG_FORMAT_MAP[log_format] + console_formatter = AutoGptFormatter(console_format_template) + else: + console_formatter = StructuredLoggingFormatter() + console_format_template = SIMPLE_LOG_FORMAT # Console output handlers stdout = logging.StreamHandler(stream=sys.stdout) - stdout.setLevel(log_level) + stdout.setLevel(level) stdout.addFilter(BelowLevelFilter(logging.WARNING)) stdout.setFormatter(console_formatter) stderr = logging.StreamHandler() stderr.setLevel(logging.WARNING) stderr.setFormatter(console_formatter) - - # INFO log file handler - activity_log_handler = logging.FileHandler(log_dir / LOG_FILE, "a", "utf-8") - activity_log_handler.setLevel(logging.INFO) - activity_log_handler.setFormatter( - AutoGptFormatter(SIMPLE_LOG_FORMAT, no_color=True) - ) - - if debug_mode: - # DEBUG log file handler - debug_log_handler = logging.FileHandler(log_dir / DEBUG_LOG_FILE, "a", "utf-8") - debug_log_handler.setLevel(logging.DEBUG) - debug_log_handler.setFormatter( - AutoGptFormatter(DEBUG_LOG_FORMAT, no_color=True) - ) - - # ERROR log file handler - error_log_handler = logging.FileHandler(log_dir / ERROR_LOG_FILE, "a", "utf-8") - error_log_handler.setLevel(logging.ERROR) - error_log_handler.setFormatter(AutoGptFormatter(DEBUG_LOG_FORMAT, no_color=True)) - - # Configure the root logger - logging.basicConfig( - format=log_format, - level=log_level, - handlers=( - [stdout, stderr, activity_log_handler, error_log_handler] - + ([debug_log_handler] if debug_mode else []) - ), - ) - - ## Set up user-friendly loggers + log_handlers += [stdout, stderr] # Console output handler which simulates typing typing_console_handler = TypingConsoleHandler(stream=sys.stdout) typing_console_handler.setLevel(logging.INFO) typing_console_handler.setFormatter(console_formatter) + # User friendly output logger (text + speech) user_friendly_output_logger = logging.getLogger(USER_FRIENDLY_OUTPUT_LOGGER) user_friendly_output_logger.setLevel(logging.INFO) user_friendly_output_logger.addHandler( - typing_console_handler if not plain_output else stdout + typing_console_handler if not plain_console_output else stdout ) if tts_config: user_friendly_output_logger.addHandler(TTSHandler(tts_config)) - user_friendly_output_logger.addHandler(activity_log_handler) - user_friendly_output_logger.addHandler(error_log_handler) user_friendly_output_logger.addHandler(stderr) user_friendly_output_logger.propagate = False + # File output handlers + if log_file_format is not None: + if level < logging.ERROR: + file_output_format_template = TEXT_LOG_FORMAT_MAP[log_file_format] + file_output_formatter = AutoGptFormatter( + file_output_format_template, no_color=True + ) + + # INFO log file handler + activity_log_handler = logging.FileHandler(log_dir / LOG_FILE, "a", "utf-8") + activity_log_handler.setLevel(level) + activity_log_handler.setFormatter(file_output_formatter) + log_handlers += [activity_log_handler] + user_friendly_output_logger.addHandler(activity_log_handler) + + # ERROR log file handler + error_log_handler = logging.FileHandler(log_dir / ERROR_LOG_FILE, "a", "utf-8") + error_log_handler.setLevel(logging.ERROR) + error_log_handler.setFormatter( + AutoGptFormatter(DEBUG_LOG_FORMAT, no_color=True) + ) + log_handlers += [error_log_handler] + user_friendly_output_logger.addHandler(error_log_handler) + + # Configure the root logger + logging.basicConfig( + format=console_format_template, + level=level, + handlers=log_handlers, + ) + + # Speech output speech_output_logger = logging.getLogger(SPEECH_OUTPUT_LOGGER) speech_output_logger.setLevel(logging.INFO) if tts_config: @@ -120,7 +184,7 @@ def configure_logging( json_logger.propagate = False # Disable debug logging from OpenAI library - openai_logger.setLevel(logging.INFO) + openai_logger.setLevel(logging.WARNING) def configure_chat_plugins(config: Config) -> None: diff --git a/autogpts/autogpt/autogpt/logs/formatters.py b/autogpts/autogpt/autogpt/logs/formatters.py index 4930d3c73..a51112573 100644 --- a/autogpts/autogpt/autogpt/logs/formatters.py +++ b/autogpts/autogpt/autogpt/logs/formatters.py @@ -1,6 +1,7 @@ import logging from colorama import Style +from google.cloud.logging_v2.handlers import CloudLoggingFilter, StructuredLogHandler from autogpt.core.runner.client_lib.logging import FancyConsoleFormatter @@ -16,7 +17,7 @@ class AutoGptFormatter(FancyConsoleFormatter): # Make sure `msg` is a string if not hasattr(record, "msg"): record.msg = "" - elif not type(record.msg) == str: + elif not type(record.msg) is str: record.msg = str(record.msg) # Strip color from the message to prevent color spoofing @@ -37,3 +38,16 @@ class AutoGptFormatter(FancyConsoleFormatter): return remove_color_codes(super().format(record)) else: return super().format(record) + + +class StructuredLoggingFormatter(StructuredLogHandler, logging.Formatter): + def __init__(self): + # Set up CloudLoggingFilter to add diagnostic info to the log records + self.cloud_logging_filter = CloudLoggingFilter() + + # Init StructuredLogHandler + super().__init__() + + def format(self, record: logging.LogRecord) -> str: + self.cloud_logging_filter.filter(record) + return super().format(record) diff --git a/autogpts/autogpt/autogpt/logs/helpers.py b/autogpts/autogpt/autogpt/logs/helpers.py index b19ee45b2..580e09a8a 100644 --- a/autogpts/autogpt/autogpt/logs/helpers.py +++ b/autogpts/autogpt/autogpt/logs/helpers.py @@ -53,10 +53,9 @@ def print_attribute( def request_user_double_check(additionalText: Optional[str] = None) -> None: if not additionalText: additionalText = ( - "Please ensure you've setup and configured everything" - " correctly. Read https://github.com/Significant-Gravitas/AutoGPT/autogpts/autogpt#readme to " - "double check. You can also create a github issue or join the discord" - " and ask there!" + "Please ensure you've setup and configured everything correctly. " + "Read https://docs.agpt.co/autogpt/setup/ to double check. " + "You can also create a github issue or join the discord and ask there!" ) user_friendly_output( diff --git a/autogpts/autogpt/autogpt/logs/utils.py b/autogpts/autogpt/autogpt/logs/utils.py index 0b92e2967..d9f39af30 100644 --- a/autogpts/autogpt/autogpt/logs/utils.py +++ b/autogpts/autogpt/autogpt/logs/utils.py @@ -3,3 +3,7 @@ import re def remove_color_codes(s: str) -> str: return re.sub(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])", "", s) + + +def fmt_kwargs(kwargs: dict) -> str: + return ", ".join(f"{n}={repr(v)}" for n, v in kwargs.items()) |