logger.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import logging
  2. import os
  3. import sys
  4. import traceback
  5. from datetime import datetime
  6. from typing import Literal, Mapping
  7. from termcolor import colored
  8. from opendevin import config
  9. from opendevin.schema.config import ConfigType
  10. DISABLE_COLOR_PRINTING = (
  11. config.get(ConfigType.DISABLE_COLOR).lower() == 'true'
  12. )
  13. ColorType = Literal[
  14. 'red',
  15. 'green',
  16. 'yellow',
  17. 'blue',
  18. 'magenta',
  19. 'cyan',
  20. 'light_grey',
  21. 'dark_grey',
  22. 'light_red',
  23. 'light_green',
  24. 'light_yellow',
  25. 'light_blue',
  26. 'light_magenta',
  27. 'light_cyan',
  28. 'white',
  29. ]
  30. LOG_COLORS: Mapping[str, ColorType] = {
  31. 'BACKGROUND LOG': 'blue',
  32. 'ACTION': 'green',
  33. 'OBSERVATION': 'yellow',
  34. 'INFO': 'cyan',
  35. 'ERROR': 'red',
  36. 'PLAN': 'light_magenta',
  37. }
  38. class ColoredFormatter(logging.Formatter):
  39. def format(self, record):
  40. msg_type = record.__dict__.get('msg_type', None)
  41. if msg_type in LOG_COLORS and not DISABLE_COLOR_PRINTING:
  42. msg_type_color = colored(msg_type, LOG_COLORS[msg_type])
  43. msg = colored(record.msg, LOG_COLORS[msg_type])
  44. time_str = colored(self.formatTime(record, self.datefmt), LOG_COLORS[msg_type])
  45. name_str = colored(record.name, 'cyan')
  46. level_str = colored(record.levelname, 'yellow')
  47. if msg_type in ['ERROR', 'INFO']:
  48. return f'{time_str} - {name_str}:{level_str}: {record.filename}:{record.lineno}\n{msg_type_color}\n{msg}'
  49. return f'{time_str} - {msg_type_color}\n{msg}'
  50. elif msg_type == 'STEP':
  51. msg = '\n\n==============\n' + record.msg + '\n'
  52. return f'{msg}'
  53. return super().format(record)
  54. console_formatter = ColoredFormatter(
  55. '\033[92m%(asctime)s - %(name)s:%(levelname)s\033[0m: %(filename)s:%(lineno)s - %(message)s',
  56. datefmt='%H:%M:%S',
  57. )
  58. file_formatter = logging.Formatter(
  59. '%(asctime)s - %(name)s:%(levelname)s: %(filename)s:%(lineno)s - %(message)s',
  60. datefmt='%H:%M:%S',
  61. )
  62. llm_formatter = logging.Formatter(
  63. '%(message)s'
  64. )
  65. def get_console_handler():
  66. """
  67. Returns a console handler for logging.
  68. """
  69. console_handler = logging.StreamHandler()
  70. console_handler.setLevel(logging.INFO)
  71. console_handler.setFormatter(console_formatter)
  72. return console_handler
  73. def get_file_handler():
  74. """
  75. Returns a file handler for logging.
  76. """
  77. log_dir = os.path.join(os.getcwd(), 'logs')
  78. os.makedirs(log_dir, exist_ok=True)
  79. timestamp = datetime.now().strftime('%Y-%m-%d')
  80. file_name = f'opendevin_{timestamp}.log'
  81. file_handler = logging.FileHandler(os.path.join(log_dir, file_name))
  82. file_handler.setLevel(logging.DEBUG)
  83. file_handler.setFormatter(file_formatter)
  84. return file_handler
  85. # Set up logging
  86. logging.basicConfig(level=logging.ERROR)
  87. def log_uncaught_exceptions(ex_cls, ex, tb):
  88. """
  89. Logs uncaught exceptions along with the traceback.
  90. Args:
  91. ex_cls (type): The type of the exception.
  92. ex (Exception): The exception instance.
  93. tb (traceback): The traceback object.
  94. Returns:
  95. None
  96. """
  97. logging.error(''.join(traceback.format_tb(tb)))
  98. logging.error('{0}: {1}'.format(ex_cls, ex))
  99. sys.excepthook = log_uncaught_exceptions
  100. opendevin_logger = logging.getLogger('opendevin')
  101. opendevin_logger.setLevel(logging.INFO)
  102. opendevin_logger.addHandler(get_file_handler())
  103. opendevin_logger.addHandler(get_console_handler())
  104. opendevin_logger.propagate = False
  105. opendevin_logger.debug('Logging initialized')
  106. opendevin_logger.debug('Logging to %s', os.path.join(
  107. os.getcwd(), 'logs', 'opendevin.log'))
  108. # Exclude LiteLLM from logging output
  109. logging.getLogger('LiteLLM').disabled = True
  110. logging.getLogger('LiteLLM Router').disabled = True
  111. logging.getLogger('LiteLLM Proxy').disabled = True
  112. class LlmFileHandler(logging.FileHandler):
  113. """
  114. # LLM prompt and response logging
  115. """
  116. def __init__(self, filename, mode='a', encoding='utf-8', delay=False):
  117. """
  118. Initializes an instance of LlmFileHandler.
  119. Args:
  120. filename (str): The name of the log file.
  121. mode (str, optional): The file mode. Defaults to 'a'.
  122. encoding (str, optional): The file encoding. Defaults to None.
  123. delay (bool, optional): Whether to delay file opening. Defaults to False.
  124. """
  125. self.filename = filename
  126. self.message_counter = 1
  127. self.session = datetime.now().strftime('%y-%m-%d_%H-%M')
  128. self.log_directory = os.path.join(os.getcwd(), 'logs', 'llm', self.session)
  129. os.makedirs(self.log_directory, exist_ok=True)
  130. filename = f'{self.filename}_{self.message_counter:03}.log'
  131. self.baseFilename = os.path.join(self.log_directory, filename)
  132. super().__init__(self.baseFilename, mode, encoding, delay)
  133. def emit(self, record):
  134. """
  135. Emits a log record.
  136. Args:
  137. record (logging.LogRecord): The log record to emit.
  138. """
  139. filename = f'{self.filename}_{self.message_counter:03}.log'
  140. self.baseFilename = os.path.join(self.log_directory, filename)
  141. self.stream = self._open()
  142. super().emit(record)
  143. self.stream.close
  144. opendevin_logger.debug('Logging to %s', self.baseFilename)
  145. self.message_counter += 1
  146. def get_llm_prompt_file_handler():
  147. """
  148. Returns a file handler for LLM prompt logging.
  149. """
  150. llm_prompt_file_handler = LlmFileHandler('prompt', delay=True)
  151. llm_prompt_file_handler.setFormatter(llm_formatter)
  152. llm_prompt_file_handler.setLevel(logging.DEBUG)
  153. return llm_prompt_file_handler
  154. def get_llm_response_file_handler():
  155. """
  156. Returns a file handler for LLM response logging.
  157. """
  158. llm_response_file_handler = LlmFileHandler('response', delay=True)
  159. llm_response_file_handler.setFormatter(llm_formatter)
  160. llm_response_file_handler.setLevel(logging.DEBUG)
  161. return llm_response_file_handler
  162. llm_prompt_logger = logging.getLogger('prompt')
  163. llm_prompt_logger.propagate = False
  164. llm_prompt_logger.setLevel(logging.DEBUG)
  165. llm_prompt_logger.addHandler(get_llm_prompt_file_handler())
  166. llm_response_logger = logging.getLogger('response')
  167. llm_response_logger.propagate = False
  168. llm_response_logger.setLevel(logging.DEBUG)
  169. llm_response_logger.addHandler(get_llm_response_file_handler())