diff --git a/cognee/shared/logging_utils.py b/cognee/shared/logging_utils.py index f4b6141a8..2a96c6f8c 100644 --- a/cognee/shared/logging_utils.py +++ b/cognee/shared/logging_utils.py @@ -69,7 +69,7 @@ class PlainFileHandler(logging.FileHandler): logger_name = record.msg.get("logger", record.name) # Format timestamp - timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + timestamp = datetime.now().strftime(get_timestamp_format()) # Create the log entry log_entry = f"{timestamp} [{record.levelname.ljust(8)}] {message}{context_str} [{logger_name}]\n" @@ -226,7 +226,7 @@ def setup_logging(log_level=None, name=None): structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), - structlog.processors.TimeStamper(fmt="iso"), + structlog.processors.TimeStamper(fmt=get_timestamp_format(), utc=True), structlog.processors.StackInfoRenderer(), exception_handler, # Add our custom exception handler structlog.processors.UnicodeDecoder(), @@ -288,9 +288,18 @@ def setup_logging(log_level=None, name=None): stream_handler.setFormatter(console_formatter) stream_handler.setLevel(log_level) + # Check if we already have a log file path from the environment + # NOTE: environment variable must be used here as it allows us to + # log to a single file with a name based on a timestamp in a multiprocess setting. + # Without it, we would have a separate log file for every process. + log_file_path = os.environ.get("LOG_FILE_NAME") + if not log_file_path: + # Create a new log file name with the cognee start time + start_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + log_file_path = os.path.join(LOGS_DIR, f"{start_time}.log") + os.environ["LOG_FILE_NAME"] = log_file_path + # Create a file handler that uses our custom PlainFileHandler - current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - log_file_path = os.path.join(LOGS_DIR, f"{current_time}.log") file_handler = PlainFileHandler(log_file_path, encoding="utf-8") file_handler.setLevel(DEBUG) @@ -328,3 +337,23 @@ def get_log_file_location(): for handler in root_logger.handlers: if isinstance(handler, logging.FileHandler): return handler.baseFilename + + +def get_timestamp_format(): + # NOTE: Some users have complained that Cognee crashes when trying to get microsecond value + # Added handler to not use microseconds if users can't access it + logger = structlog.get_logger() + try: + # We call datetime.now() here to test if microseconds are supported. + # If they are not supported a ValueError will be raised + datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f") + return "%Y-%m-%dT%H:%M:%S.%f" + except Exception as e: + logger.debug(f"Exception caught: {e}") + logger.debug( + "Could not use microseconds for the logging timestamp, defaulting to use hours minutes and seconds only" + ) + # We call datetime.now() here to test if won't break. + datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + # We return the timestamp format without microseconds as they are not supported + return "%Y-%m-%dT%H:%M:%S"