diff --git a/cognee-mcp/src/server.py b/cognee-mcp/src/server.py index fffbaf223..456c64cee 100755 --- a/cognee-mcp/src/server.py +++ b/cognee-mcp/src/server.py @@ -4,7 +4,7 @@ import sys import argparse import cognee import asyncio -from cognee.shared.logging_utils import get_logger, get_log_file_location +from cognee.shared.logging_utils import get_logger, setup_logging, get_log_file_location import importlib.util from contextlib import redirect_stdout import mcp.types as types @@ -20,7 +20,6 @@ from cognee.modules.storage.utils import JSONEncoder mcp = FastMCP("Cognee") logger = get_logger() -log_file = get_log_file_location() @mcp.tool() @@ -91,7 +90,7 @@ async def cognee_add_developer_rules( tasks.append(asyncio.create_task(cognify_task(abs_path))) else: logger.warning(f"Skipped missing developer rule file: {abs_path}") - + log_file = get_log_file_location() return [ types.TextContent( type="text", @@ -173,6 +172,7 @@ async def cognify(data: str, graph_model_file: str = None, graph_model_name: str ) ) + log_file = get_log_file_location() text = ( f"Background process launched due to MCP timeout limitations.\n" f"To check current cognify status use the cognify_status tool\n" @@ -234,6 +234,7 @@ async def codify(repo_path: str) -> list: asyncio.create_task(codify_task(repo_path)) + log_file = get_log_file_location() text = ( f"Background process launched due to MCP timeout limitations.\n" f"To check current codify status use the codify_status tool\n" @@ -454,6 +455,8 @@ async def main(): if __name__ == "__main__": + logger = setup_logging() + try: asyncio.run(main()) except Exception as e: diff --git a/cognee/__init__.py b/cognee/__init__.py index ee637ae9b..7aa6388d9 100644 --- a/cognee/__init__.py +++ b/cognee/__init__.py @@ -1,9 +1,20 @@ +# ruff: noqa: E402 from cognee.version import get_cognee_version # NOTE: __version__ extraction must be at the top of the __init__.py otherwise # there will be circular import issues __version__ = get_cognee_version() +# Load environment variable settings has to be before setting up logging for LOG_LEVEL value +import dotenv + +dotenv.load_dotenv(override=True) + +# NOTE: Log level can be set with the LOG_LEVEL env variable +from cognee.shared.logging_utils import setup_logging + +logger = setup_logging() + from .api.v1.add import add from .api.v1.delete import delete from .api.v1.cognify import cognify @@ -18,7 +29,3 @@ from cognee.modules.visualization.cognee_network_visualization import ( # Pipelines from .modules import pipelines - -import dotenv - -dotenv.load_dotenv(override=True) diff --git a/cognee/api/client.py b/cognee/api/client.py index f813031fd..a3145d896 100644 --- a/cognee/api/client.py +++ b/cognee/api/client.py @@ -3,7 +3,7 @@ import os import uvicorn -from cognee.shared.logging_utils import get_logger +from cognee.shared.logging_utils import get_logger, setup_logging import sentry_sdk from fastapi import FastAPI, status from fastapi.responses import JSONResponse, Response @@ -195,4 +195,5 @@ def start_api_server(host: str = "0.0.0.0", port: int = 8000): if __name__ == "__main__": + logger = setup_logging() start_api_server() diff --git a/cognee/api/v1/cognify/code_graph_pipeline.py b/cognee/api/v1/cognify/code_graph_pipeline.py index 90858981e..a3dabe1e2 100644 --- a/cognee/api/v1/cognify/code_graph_pipeline.py +++ b/cognee/api/v1/cognify/code_graph_pipeline.py @@ -2,7 +2,7 @@ import os import pathlib import asyncio from uuid import NAMESPACE_OID, uuid5 -from cognee.shared.logging_utils import get_logger +from cognee.shared.logging_utils import get_logger, setup_logging from cognee.modules.observability.get_observe import get_observe from cognee.api.v1.search import SearchType, search @@ -97,4 +97,5 @@ if __name__ == "__main__": for file in search_results: print(file["name"]) + logger = setup_logging(name="code_graph_pipeline") asyncio.run(main()) diff --git a/cognee/api/v1/visualize/visualize.py b/cognee/api/v1/visualize/visualize.py index dad153879..583530f92 100644 --- a/cognee/api/v1/visualize/visualize.py +++ b/cognee/api/v1/visualize/visualize.py @@ -2,7 +2,7 @@ from cognee.modules.visualization.cognee_network_visualization import ( cognee_network_visualization, ) from cognee.infrastructure.databases.graph import get_graph_engine -from cognee.shared.logging_utils import get_logger, ERROR +from cognee.shared.logging_utils import get_logger, setup_logging, ERROR import asyncio @@ -28,7 +28,7 @@ async def visualize_graph(destination_file_path: str = None): if __name__ == "__main__": - logger = get_logger(level=ERROR) + logger = setup_logging(log_level=ERROR) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: diff --git a/cognee/modules/retrieval/utils/description_to_codepart_search.py b/cognee/modules/retrieval/utils/description_to_codepart_search.py index 1aaf084d4..63256891d 100644 --- a/cognee/modules/retrieval/utils/description_to_codepart_search.py +++ b/cognee/modules/retrieval/utils/description_to_codepart_search.py @@ -1,5 +1,5 @@ import asyncio -from cognee.shared.logging_utils import get_logger, ERROR +from cognee.shared.logging_utils import get_logger, setup_logging, ERROR from typing import List from cognee.infrastructure.databases.graph import get_graph_engine @@ -144,6 +144,7 @@ async def code_description_to_code_part( if __name__ == "__main__": + logger = setup_logging(log_level=ERROR) async def main(): query = "I am looking for a class with blue eyes" diff --git a/cognee/shared/logging_utils.py b/cognee/shared/logging_utils.py index 26bea77d9..734fa1ace 100644 --- a/cognee/shared/logging_utils.py +++ b/cognee/shared/logging_utils.py @@ -1,6 +1,5 @@ import os import sys -import threading import logging import structlog import traceback @@ -10,6 +9,7 @@ from pathlib import Path import importlib.metadata from cognee import __version__ as cognee_version +from typing import Protocol # Export common log levels DEBUG = logging.DEBUG @@ -27,11 +27,8 @@ log_levels = { "NOTSET": logging.NOTSET, } -# Track if logging has been configured -_is_configured = False - -# Create a lock for thread-safe initialization -_setup_lock = threading.Lock() +# Track if structlog logging has been configured +_is_structlog_configured = False # Path to logs directory LOGS_DIR = Path(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "logs")) @@ -128,45 +125,27 @@ class PlainFileHandler(logging.FileHandler): self.flush() -class LoggerInterface: - def info(self, msg, *args, **kwargs): - pass - - def warning(self, msg, *args, **kwargs): - pass - - def error(self, msg, *args, **kwargs): - pass - - def critical(self, msg, *args, **kwargs): - pass - - def debug(self, msg, *args, **kwargs): - pass +class LoggerInterface(Protocol): + def info(self, msg: str, *args, **kwargs) -> None: ... + def warning(self, msg: str, *args, **kwargs) -> None: ... + def error(self, msg: str, *args, **kwargs) -> None: ... + def critical(self, msg: str, *args, **kwargs) -> None: ... + def debug(self, msg: str, *args, **kwargs) -> None: ... def get_logger(name=None, level=None) -> LoggerInterface: - """Get a configured structlog logger. + """Get a logger. - Args: - name: Logger name (default: None, uses __name__) - level: Logging level (default: None) - - Returns: - A configured structlog logger instance + If `setup_logging()` has not been called, returns a standard Python logger. + If `setup_logging()` has been called, returns a structlog logger. """ - global _is_configured - - # Always first check if logger is already configured to not use threading lock if not necessary - if not _is_configured: - # Use threading lock to make sure setup_logging can be called only once - with _setup_lock: - # Unfortunately we also need a second check in case lock was entered twice at the same time - if not _is_configured: - setup_logging(level) - _is_configured = True - - return structlog.get_logger(name if name else __name__) + if _is_structlog_configured: + return structlog.get_logger(name if name else __name__) + else: + logger = logging.getLogger(name if name else __name__) + if level is not None: + logger.setLevel(level) + return logger def cleanup_old_logs(logs_dir, max_files): @@ -177,9 +156,8 @@ def cleanup_old_logs(logs_dir, max_files): logs_dir: Directory containing log files max_files: Maximum number of log files to keep """ + logger = structlog.get_logger() try: - logger = structlog.get_logger() - # Get all .log files in the directory (excluding README and other files) log_files = [f for f in logs_dir.glob("*.log") if f.is_file()] @@ -211,6 +189,7 @@ def setup_logging(log_level=None, name=None): Returns: A configured structlog logger instance """ + global _is_structlog_configured log_level = log_level if log_level else log_levels[os.getenv("LOG_LEVEL", "INFO")] @@ -260,9 +239,11 @@ def setup_logging(log_level=None, name=None): logger = structlog.get_logger() logger.error( - "Uncaught exception", + "Exception", exc_info=(exc_type, exc_value, traceback), ) + # Hand back to the original hook → prints traceback and exits + sys.__excepthook__(exc_type, exc_value, traceback) # Install exception handlers sys.excepthook = handle_exception @@ -325,6 +306,7 @@ def setup_logging(log_level=None, name=None): if log_level > logging.DEBUG: import warnings + from sqlalchemy.exc import SAWarning warnings.filterwarnings( @@ -337,6 +319,9 @@ def setup_logging(log_level=None, name=None): # Clean up old log files, keeping only the most recent ones cleanup_old_logs(LOGS_DIR, MAX_LOG_FILES) + # Mark logging as configured + _is_structlog_configured = True + # Get a configured logger and log system information logger = structlog.get_logger(name if name else __name__) logger.info( @@ -352,7 +337,7 @@ def setup_logging(log_level=None, name=None): def get_log_file_location(): - # Get the root logger + """Return the file path of the log file in use, if any.""" root_logger = logging.getLogger() # Loop through handlers to find the FileHandler diff --git a/examples/python/code_graph_example.py b/examples/python/code_graph_example.py index 13e2d822f..431069050 100644 --- a/examples/python/code_graph_example.py +++ b/examples/python/code_graph_example.py @@ -2,7 +2,7 @@ import argparse import asyncio import cognee from cognee import SearchType -from cognee.shared.logging_utils import get_logger, ERROR +from cognee.shared.logging_utils import setup_logging, ERROR from cognee.api.v1.cognify.code_graph_pipeline import run_code_graph_pipeline @@ -41,7 +41,7 @@ def parse_args(): if __name__ == "__main__": - logger = get_logger(level=ERROR) + logger = setup_logging(log_level=ERROR) args = parse_args() diff --git a/examples/python/dynamic_steps_example.py b/examples/python/dynamic_steps_example.py index fd61be9ca..bce2ea8be 100644 --- a/examples/python/dynamic_steps_example.py +++ b/examples/python/dynamic_steps_example.py @@ -1,8 +1,8 @@ -import cognee import asyncio -from cognee.shared.logging_utils import get_logger, ERROR +import cognee from cognee.api.v1.search import SearchType +from cognee.shared.logging_utils import setup_logging, ERROR job_1 = """ CV 1: Relevant @@ -191,7 +191,7 @@ async def main(enable_steps): if __name__ == "__main__": - logger = get_logger(level=ERROR) + logger = setup_logging(log_level=ERROR) rebuild_kg = True retrieve = True diff --git a/examples/python/graphiti_example.py b/examples/python/graphiti_example.py index 5ad964fcd..4167e8391 100644 --- a/examples/python/graphiti_example.py +++ b/examples/python/graphiti_example.py @@ -1,7 +1,7 @@ import asyncio import cognee -from cognee.shared.logging_utils import get_logger, ERROR +from cognee.shared.logging_utils import setup_logging, ERROR from cognee.modules.pipelines import Task, run_tasks from cognee.tasks.temporal_awareness import build_graph_with_temporal_awareness from cognee.infrastructure.databases.relational import ( @@ -74,7 +74,7 @@ async def main(): if __name__ == "__main__": - logger = get_logger(level=ERROR) + logger = setup_logging(log_level=ERROR) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: diff --git a/examples/python/multimedia_example.py b/examples/python/multimedia_example.py index c38b746ef..dd7260a15 100644 --- a/examples/python/multimedia_example.py +++ b/examples/python/multimedia_example.py @@ -1,7 +1,7 @@ import os import asyncio import pathlib -from cognee.shared.logging_utils import get_logger, ERROR +from cognee.shared.logging_utils import setup_logging, ERROR import cognee from cognee.api.v1.search import SearchType @@ -46,7 +46,7 @@ async def main(): if __name__ == "__main__": - logger = get_logger(level=ERROR) + logger = setup_logging(log_level=ERROR) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: diff --git a/examples/python/ontology_demo_example.py b/examples/python/ontology_demo_example.py index fcc6d9373..8243faef5 100644 --- a/examples/python/ontology_demo_example.py +++ b/examples/python/ontology_demo_example.py @@ -1,11 +1,10 @@ -import cognee import asyncio -from cognee.shared.logging_utils import get_logger import os +import cognee from cognee.api.v1.search import SearchType from cognee.api.v1.visualize.visualize import visualize_graph - +from cognee.shared.logging_utils import setup_logging text_1 = """ 1. Audi @@ -75,7 +74,7 @@ async def main(): if __name__ == "__main__": - logger = get_logger() + logger = setup_logging() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) diff --git a/examples/python/ontology_demo_example_2.py b/examples/python/ontology_demo_example_2.py index 084762a77..22fb19862 100644 --- a/examples/python/ontology_demo_example_2.py +++ b/examples/python/ontology_demo_example_2.py @@ -1,6 +1,6 @@ import cognee import asyncio -from cognee.shared.logging_utils import get_logger +from cognee.shared.logging_utils import setup_logging import os import textwrap from cognee.api.v1.search import SearchType @@ -92,7 +92,7 @@ async def main(): if __name__ == "__main__": - logger = get_logger() + logger = setup_logging() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) diff --git a/examples/python/simple_example.py b/examples/python/simple_example.py index 148f54325..41be06f25 100644 --- a/examples/python/simple_example.py +++ b/examples/python/simple_example.py @@ -1,6 +1,6 @@ import asyncio import cognee -from cognee.shared.logging_utils import get_logger, ERROR +from cognee.shared.logging_utils import setup_logging, ERROR from cognee.api.v1.search import SearchType # Prerequisites: @@ -67,7 +67,7 @@ async def main(): if __name__ == "__main__": - logger = get_logger(level=ERROR) + logger = setup_logging(log_level=ERROR) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: diff --git a/examples/python/simple_node_set_example.py b/examples/python/simple_node_set_example.py index 471e51f7e..d1ad4fae0 100644 --- a/examples/python/simple_node_set_example.py +++ b/examples/python/simple_node_set_example.py @@ -2,7 +2,7 @@ import os import asyncio import cognee from cognee.api.v1.visualize.visualize import visualize_graph -from cognee.shared.logging_utils import get_logger, ERROR +from cognee.shared.logging_utils import setup_logging, ERROR text_a = """ AI is revolutionizing financial services through intelligent fraud detection @@ -39,6 +39,6 @@ async def main(): if __name__ == "__main__": - logger = get_logger(level=ERROR) + logger = setup_logging(log_level=ERROR) loop = asyncio.new_event_loop() asyncio.run(main())