mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
feat: Beta work (#17)
feat: Add multiple projects support feat: enhanced read_note for when initial result is not found fix: merge frontmatter when updating note fix: handle directory removed on sync watch
This commit is contained in:
+52
-38
@@ -1,30 +1,43 @@
|
||||
"""Utility functions for basic-memory."""
|
||||
|
||||
import logging
|
||||
# Set environment variable before importing logfire to suppress warnings
|
||||
import os
|
||||
|
||||
os.environ["LOGFIRE_IGNORE_NO_CONFIG"] = "1"
|
||||
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Protocol, Union, runtime_checkable
|
||||
|
||||
from loguru import logger
|
||||
from unidecode import unidecode
|
||||
|
||||
import basic_memory
|
||||
|
||||
import logfire
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class PathLike(Protocol):
|
||||
"""Protocol for objects that can be used as paths."""
|
||||
|
||||
def __str__(self) -> str: ...
|
||||
|
||||
|
||||
# In type annotations, use Union[Path, str] instead of FilePath for now
|
||||
# This preserves compatibility with existing code while we migrate
|
||||
FilePath = Union[Path, str]
|
||||
|
||||
# Disable the "Queue is full" warning
|
||||
logging.getLogger("opentelemetry.sdk.metrics._internal.instrument").setLevel(logging.ERROR)
|
||||
# Disable logfire prompts in CI/automated environments
|
||||
os.environ["LOGFIRE_IGNORE_NO_CONFIG"] = "1"
|
||||
|
||||
|
||||
def generate_permalink(file_path: Union[Path, str]) -> str:
|
||||
def generate_permalink(file_path: Union[Path, str, PathLike]) -> str:
|
||||
"""Generate a stable permalink from a file path.
|
||||
|
||||
Args:
|
||||
file_path: Original file path
|
||||
file_path: Original file path (str, Path, or PathLike)
|
||||
|
||||
Returns:
|
||||
Normalized permalink that matches validation rules. Converts spaces and underscores
|
||||
@@ -78,44 +91,45 @@ def setup_logging(
|
||||
) -> None: # pragma: no cover
|
||||
"""
|
||||
Configure logging for the application.
|
||||
:param home_dir: the root directory for the application
|
||||
:param log_file: the name of the log file to write to
|
||||
:param app: the fastapi application instance
|
||||
:param console: whether to log to the console
|
||||
"""
|
||||
|
||||
Args:
|
||||
env: The environment name (dev, test, prod)
|
||||
home_dir: The root directory for the application
|
||||
log_file: The name of the log file to write to
|
||||
log_level: The logging level to use
|
||||
console: Whether to log to the console
|
||||
"""
|
||||
# Remove default handler and any existing handlers
|
||||
logger.remove()
|
||||
|
||||
# Add file handler if we are not running tests
|
||||
# Add file handler if we are not running tests and a log file is specified
|
||||
if log_file and env != "test":
|
||||
try:
|
||||
# Skip logfire configuration if LOGFIRE_API_KEY is not set
|
||||
# This avoids interactive prompts when running automated tasks
|
||||
if "LOGFIRE_API_KEY" in os.environ:
|
||||
# enable pydantic logfire
|
||||
# Only configure logfire if API key is set - avoids interactive prompts
|
||||
if "LOGFIRE_TOKEN" in os.environ:
|
||||
# Configure logfire with code source info
|
||||
logfire.configure(
|
||||
code_source=logfire.CodeSource(
|
||||
repository="https://github.com/basicmachines-co/basic-memory",
|
||||
revision=basic_memory.__version__,
|
||||
revision=f"v{basic_memory.__version__}" if env != "dev" else "HEAD",
|
||||
),
|
||||
environment=env,
|
||||
console=False,
|
||||
)
|
||||
logger.configure(handlers=[logfire.loguru_handler()])
|
||||
|
||||
# instrument code spans
|
||||
# Instrument code spans for better observability
|
||||
logfire.instrument_sqlite3()
|
||||
logfire.instrument_httpx()
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to configure logfire: {e}")
|
||||
|
||||
# setup logger
|
||||
# Setup file logger
|
||||
log_path = home_dir / log_file
|
||||
logger.add(
|
||||
str(log_path),
|
||||
level=log_level,
|
||||
rotation="100 MB",
|
||||
rotation="10 MB",
|
||||
retention="10 days",
|
||||
backtrace=True,
|
||||
diagnose=True,
|
||||
@@ -123,26 +137,26 @@ def setup_logging(
|
||||
colorize=False,
|
||||
)
|
||||
|
||||
# Add console logger if requested or in test mode
|
||||
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: '{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)
|
||||
# Reduce noise from third-party libraries
|
||||
noisy_loggers = {
|
||||
# HTTP client logs
|
||||
"httpx": logging.WARNING,
|
||||
# File watching logs
|
||||
"watchfiles.main": logging.WARNING,
|
||||
# Instrumentation noise
|
||||
"instrumentor": logging.ERROR,
|
||||
"opentelemetry.instrumentation.instrumentor": logging.ERROR,
|
||||
"opentelemetry.instrumentation": logging.ERROR,
|
||||
"logfire.instrumentor": logging.ERROR,
|
||||
"opentelemetry.sdk.metrics._internal.instrument": logging.ERROR,
|
||||
}
|
||||
|
||||
# turn watchfiles to WARNING
|
||||
logging.getLogger("watchfiles.main").setLevel(logging.WARNING)
|
||||
|
||||
# Disable all instrumentor-related warnings
|
||||
for logger_name in [
|
||||
"instrumentor",
|
||||
"opentelemetry.instrumentation.instrumentor",
|
||||
"opentelemetry.instrumentation",
|
||||
"logfire.instrumentor",
|
||||
"opentelemetry.sdk.metrics._internal.instrument",
|
||||
]:
|
||||
logging.getLogger(logger_name).setLevel(logging.ERROR)
|
||||
# Set log levels for noisy loggers
|
||||
for logger_name, level in noisy_loggers.items():
|
||||
logging.getLogger(logger_name).setLevel(level)
|
||||
|
||||
Reference in New Issue
Block a user