diff --git a/src/basic_memory/api/app.py b/src/basic_memory/api/app.py index a111b899..07a01f1c 100644 --- a/src/basic_memory/api/app.py +++ b/src/basic_memory/api/app.py @@ -7,18 +7,14 @@ from fastapi import FastAPI, HTTPException from fastapi.exception_handlers import http_exception_handler from loguru import logger -import basic_memory from basic_memory import db from basic_memory.config import config as app_config from basic_memory.api.routers import knowledge, search, memory, resource -from basic_memory.utils import setup_logging @asynccontextmanager async def lifespan(app: FastAPI): # pragma: no cover """Lifecycle manager for the FastAPI app.""" - setup_logging(log_file=".basic-memory/basic-memory.log") - logger.info(f"Starting Basic Memory API {basic_memory.__version__}") await db.run_migrations(app_config) yield logger.info("Shutting down Basic Memory API") diff --git a/src/basic_memory/cli/app.py b/src/basic_memory/cli/app.py index 8fa0cbc1..bfc0e661 100644 --- a/src/basic_memory/cli/app.py +++ b/src/basic_memory/cli/app.py @@ -4,9 +4,7 @@ import typer from basic_memory import db from basic_memory.config import config -from basic_memory.utils import setup_logging -setup_logging(log_file=".basic-memory/basic-memory-cli.log", console=False) # pragma: no cover asyncio.run(db.run_migrations(config)) diff --git a/src/basic_memory/cli/commands/mcp.py b/src/basic_memory/cli/commands/mcp.py index 79cc3d7d..8bd53150 100644 --- a/src/basic_memory/cli/commands/mcp.py +++ b/src/basic_memory/cli/commands/mcp.py @@ -17,4 +17,4 @@ def mcp(): # pragma: no cover home_dir = config.home logger.info(f"Starting Basic Memory MCP server {basic_memory.__version__}") logger.info(f"Home directory: {home_dir}") - mcp_server.run() + mcp_server.run() \ No newline at end of file diff --git a/src/basic_memory/cli/commands/sync.py b/src/basic_memory/cli/commands/sync.py index 71d66be2..20b442e3 100644 --- a/src/basic_memory/cli/commands/sync.py +++ b/src/basic_memory/cli/commands/sync.py @@ -203,4 +203,4 @@ def sync( logger.exception("Sync failed") typer.echo(f"Error during sync: {e}", err=True) raise typer.Exit(1) - raise + raise \ No newline at end of file diff --git a/src/basic_memory/cli/commands/tools.py b/src/basic_memory/cli/commands/tools.py index 75a31410..28af938c 100644 --- a/src/basic_memory/cli/commands/tools.py +++ b/src/basic_memory/cli/commands/tools.py @@ -72,7 +72,7 @@ def build_context( max_related=max_related, ) ) - rprint(context.model_dump()) + rprint(context.model_dump_json(indent=2)) except Exception as e: # pragma: no cover if not isinstance(e, typer.Exit): typer.echo(f"Error during build_context: {e}", err=True) @@ -105,7 +105,7 @@ def recent_activity( max_related=max_related, ) ) - rprint(context.model_dump()) + rprint(context.model_dump_json(indent=2)) except Exception as e: # pragma: no cover if not isinstance(e, typer.Exit): typer.echo(f"Error during build_context: {e}", err=True) @@ -137,7 +137,7 @@ def search( after_date=after_date, ) results = asyncio.run(mcp_search(query=search_query, page=page, page_size=page_size)) - rprint(results.model_dump()) + rprint(results.model_dump_json(indent=2)) except Exception as e: # pragma: no cover if not isinstance(e, typer.Exit): typer.echo(f"Error during search: {e}", err=True) @@ -149,7 +149,7 @@ def search( def get_entity(identifier: str): try: entity = asyncio.run(mcp_get_entity(identifier=identifier)) - rprint(entity.model_dump()) + rprint(entity.model_dump_json(indent=2)) except Exception as e: # pragma: no cover if not isinstance(e, typer.Exit): typer.echo(f"Error during get_entity: {e}", err=True) diff --git a/src/basic_memory/cli/main.py b/src/basic_memory/cli/main.py index 18aed9c0..d25dde73 100644 --- a/src/basic_memory/cli/main.py +++ b/src/basic_memory/cli/main.py @@ -15,6 +15,5 @@ from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover tools, ) - if __name__ == "__main__": # pragma: no cover app() diff --git a/src/basic_memory/config.py b/src/basic_memory/config.py index 6e74b56a..a8d2264f 100644 --- a/src/basic_memory/config.py +++ b/src/basic_memory/config.py @@ -3,9 +3,13 @@ from pathlib import Path from typing import Literal +from loguru import logger from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict +import basic_memory +from basic_memory.utils import setup_logging + DATABASE_NAME = "memory.db" DATA_DIR_NAME = ".basic-memory" @@ -60,3 +64,13 @@ class ProjectConfig(BaseSettings): # Load project config config = ProjectConfig() + +# setup logging +setup_logging( + env=config.env, + home_dir=config.home, + log_level=config.log_level, + log_file=".basic-memory/basic-memory.log", + console=False, +) +logger.info(f"Starting Basic Memory {basic_memory.__version__}") diff --git a/src/basic_memory/mcp/server.py b/src/basic_memory/mcp/server.py index 2811d47e..ca9b5710 100644 --- a/src/basic_memory/mcp/server.py +++ b/src/basic_memory/mcp/server.py @@ -1,15 +1,11 @@ """Enhanced FastMCP server instance for Basic Memory.""" from mcp.server.fastmcp import FastMCP - -from basic_memory.utils import setup_logging +from mcp.server.fastmcp.utilities.logging import configure_logging # mcp console logging -# configure_logging(level='INFO') +configure_logging(level="INFO") -# start our out file logging -setup_logging(log_file=".basic-memory/basic-memory.log") - # Create the shared server instance mcp = FastMCP("Basic Memory") diff --git a/src/basic_memory/utils.py b/src/basic_memory/utils.py index 4e8d1227..7204cfb3 100644 --- a/src/basic_memory/utils.py +++ b/src/basic_memory/utils.py @@ -1,5 +1,5 @@ """Utility functions for basic-memory.""" - +import logging import os import re import sys @@ -10,7 +10,6 @@ from loguru import logger from unidecode import unidecode import basic_memory -from basic_memory.config import config import logfire @@ -65,8 +64,8 @@ def generate_permalink(file_path: Union[Path, str]) -> str: def setup_logging( - home_dir: Path = config.home, log_file: Optional[str] = None, console: bool = True -) -> None: # pragma: no cover + env: str, home_dir: Path, log_file: Optional[str] = None, log_level: str = "INFO", console: bool = True +, ) -> None: # pragma: no cover """ Configure logging for the application. :param home_dir: the root directory for the application @@ -79,15 +78,14 @@ def setup_logging( logger.remove() # Add file handler if we are not running tests - if log_file and config.env != "test": + if log_file and env != "test": # enable pydantic logfire logfire.configure( code_source=logfire.CodeSource( repository="https://github.com/basicmachines-co/basic-memory", revision=basic_memory.__version__, - root_path="/src/basic_memory", ), - environment=config.env, + environment=env, console=False, ) logger.configure(handlers=[logfire.loguru_handler()]) @@ -100,7 +98,7 @@ def setup_logging( log_path = home_dir / log_file logger.add( str(log_path), - level=config.log_level, + level=log_level, rotation="100 MB", retention="10 days", backtrace=True, @@ -109,7 +107,13 @@ def setup_logging( colorize=False, ) - # Add stderr handler - logger.add(sys.stderr, level=config.log_level, backtrace=True, diagnose=True, colorize=True) + if env == "test" or console: + # Add stderr handler + logger.add(sys.stderr, level=log_level, backtrace=True, diagnose=True, colorize=True) - logger.info(f"ENV: '{config.env}' Log level: '{config.log_level}' Logging to {log_file}") + logger.info(f"ENV: '{env}' Log level: '{log_level}' Logging to {log_file}") + + # Get the logger for 'httpx' + httpx_logger = logging.getLogger("httpx") + # Set the logging level to WARNING to ignore INFO and DEBUG logs + httpx_logger.setLevel(logging.WARNING) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 9d7eaeae..80561ae8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -from basic_memory.config import config +import os # set config.env to "test" for pytest to prevent logging to file in utils.setup_logging() -config.env = "test" +os.environ["BASIC_MEMORY_ENV"] = "test" diff --git a/uv.lock b/uv.lock index 93e27bfa..e042aa03 100644 --- a/uv.lock +++ b/uv.lock @@ -79,7 +79,7 @@ wheels = [ [[package]] name = "basic-memory" -version = "0.6.0" +version = "0.7.0" source = { editable = "." } dependencies = [ { name = "aiosqlite" },