diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 5d4b9ff79..07c4bfc0d 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -185,6 +185,10 @@ class Crew(BaseModel): output_log_file: Optional[str] = Field( default=None, description="output_log_file", + ), + save_as_json: Optional[bool] = Field( + default=False, + description="If true saves the logs in JSON format", ) planning: Optional[bool] = Field( default=False, @@ -244,7 +248,7 @@ class Crew(BaseModel): self._cache_handler = CacheHandler() self._logger = Logger(verbose=self.verbose) if self.output_log_file: - self._file_handler = FileHandler(self.output_log_file) + self._file_handler = FileHandler(self.output_log_file,self.save_as_json) self._rpm_controller = RPMController(max_rpm=self.max_rpm, logger=self._logger) if self.function_calling_llm and not isinstance(self.function_calling_llm, LLM): self.function_calling_llm = create_llm(self.function_calling_llm) diff --git a/src/crewai/utilities/file_handler.py b/src/crewai/utilities/file_handler.py index bb97b940f..4600afa02 100644 --- a/src/crewai/utilities/file_handler.py +++ b/src/crewai/utilities/file_handler.py @@ -5,24 +5,48 @@ from datetime import datetime class FileHandler: """take care of file operations, currently it only logs messages to a file""" - - def __init__(self, file_path): - if isinstance(file_path, bool): - self._path = os.path.join(os.curdir, "logs.txt") - elif isinstance(file_path, str): + + def __init__(self, file_path, save_as_json): + self.save_as_json = save_as_json + if file_path is True: # File path is boolean True + if save_as_json: + self._path = os.path.join(os.curdir, "logs.json") + else: + self._path = os.path.join(os.curdir, "logs.txt") + elif isinstance(file_path, str): # File path is a string + if save_as_json: + if not file_path.endswith(".json"): + file_path += ".json" self._path = file_path else: raise ValueError("file_path must be either a boolean or a string.") def log(self, **kwargs): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - message = ( - f"{now}: " - + ", ".join([f'{key}="{value}"' for key, value in kwargs.items()]) - + "\n" - ) - with open(self._path, "a", encoding="utf-8") as file: - file.write(message + "\n") + log_entry = {"timestamp": now, **kwargs} + + if self._path.endswith(".json"): + # Append log in JSON format + with open(self._path, "a", encoding="utf-8") as file: + # If the file is empty, start with a list; else, append to it + try: + # Try reading existing content to avoid overwriting + with open(self._path, "r", encoding="utf-8") as read_file: + existing_data = json.load(read_file) + existing_data.append(log_entry) + except (json.JSONDecodeError, FileNotFoundError): + # If no valid JSON or file doesn't exist, start with an empty list + existing_data = [log_entry] + + with open(self._path, "w", encoding="utf-8") as write_file: + json.dump(existing_data, write_file, indent=4) + write_file.write("\n") + else: + # Append log in plain text format + message = f"{now}: " + ", ".join([f"{key}=\"{value}\"" for key, value in kwargs.items()]) + "\n" + with open(self._path, "a", encoding="utf-8") as file: + file.write(message) + class PickleHandler: