fix(config): log configuration loading errors instead of silently ignoring

Replaces bare exception handler in load_settings() with logging.warning()
to help users debug configuration file issues (syntax errors, permissions).
Maintains backward compatibility - errors do not break initialization.

Solution-ID: SOL-1735385400001
Issue-ID: ISS-1766921318981-1
Task-ID: T1
This commit is contained in:
catlog22
2025-12-28 21:06:23 +08:00
parent 58caccb250
commit 93dcdd2293
2 changed files with 115 additions and 3 deletions

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import logging
import os import os
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import cached_property from functools import cached_property
@@ -18,6 +19,8 @@ WORKSPACE_DIR_NAME = ".codexlens"
# Settings file name # Settings file name
SETTINGS_FILE_NAME = "settings.json" SETTINGS_FILE_NAME = "settings.json"
log = logging.getLogger(__name__)
def _default_global_dir() -> Path: def _default_global_dir() -> Path:
"""Get global CodexLens data directory.""" """Get global CodexLens data directory."""
@@ -200,7 +203,15 @@ class Config:
# Load embedding settings # Load embedding settings
embedding = settings.get("embedding", {}) embedding = settings.get("embedding", {})
if "backend" in embedding: if "backend" in embedding:
self.embedding_backend = embedding["backend"] backend = embedding["backend"]
if backend in {"fastembed", "litellm"}:
self.embedding_backend = backend
else:
log.warning(
"Invalid embedding backend in %s: %r (expected 'fastembed' or 'litellm')",
self.settings_path,
backend,
)
if "model" in embedding: if "model" in embedding:
self.embedding_model = embedding["model"] self.embedding_model = embedding["model"]
if "use_gpu" in embedding: if "use_gpu" in embedding:
@@ -224,8 +235,13 @@ class Config:
self.llm_timeout_ms = llm["timeout_ms"] self.llm_timeout_ms = llm["timeout_ms"]
if "batch_size" in llm: if "batch_size" in llm:
self.llm_batch_size = llm["batch_size"] self.llm_batch_size = llm["batch_size"]
except Exception: except Exception as exc:
pass # Silently ignore errors log.warning(
"Failed to load settings from %s (%s): %s",
self.settings_path,
type(exc).__name__,
exc,
)
@classmethod @classmethod
def load(cls) -> "Config": def load(cls) -> "Config":

View File

@@ -1,5 +1,8 @@
"""Tests for CodexLens configuration system.""" """Tests for CodexLens configuration system."""
import builtins
import json
import logging
import os import os
import tempfile import tempfile
from pathlib import Path from pathlib import Path
@@ -224,6 +227,99 @@ class TestConfig:
del os.environ["CODEXLENS_DATA_DIR"] del os.environ["CODEXLENS_DATA_DIR"]
class TestConfigLoadSettings:
"""Tests for Config.load_settings behavior and logging."""
def test_load_settings_logs_warning_on_malformed_json(self, caplog):
"""Malformed JSON in settings file should trigger warning log."""
with tempfile.TemporaryDirectory() as tmpdir:
config = Config(data_dir=Path(tmpdir))
config.settings_path.write_text("{", encoding="utf-8")
with caplog.at_level(logging.WARNING):
config.load_settings()
records = [r for r in caplog.records if r.name == "codexlens.config"]
assert any("Failed to load settings from" in r.message for r in records)
assert any("JSONDecodeError" in r.message for r in records)
assert any(str(config.settings_path) in r.message for r in records)
def test_load_settings_logs_warning_on_permission_error(self, monkeypatch, caplog):
"""Permission errors opening settings file should trigger warning log."""
with tempfile.TemporaryDirectory() as tmpdir:
config = Config(data_dir=Path(tmpdir))
config.settings_path.write_text("{}", encoding="utf-8")
real_open = builtins.open
def guarded_open(path, mode="r", *args, **kwargs):
if Path(path) == config.settings_path and "r" in mode:
raise PermissionError("Permission denied")
return real_open(path, mode, *args, **kwargs)
monkeypatch.setattr(builtins, "open", guarded_open)
with caplog.at_level(logging.WARNING):
config.load_settings()
records = [r for r in caplog.records if r.name == "codexlens.config"]
assert any("Failed to load settings from" in r.message for r in records)
assert any("PermissionError" in r.message for r in records)
def test_load_settings_loads_valid_settings_without_warning(self, caplog):
"""Valid settings should load without warning logs."""
with tempfile.TemporaryDirectory() as tmpdir:
config = Config(data_dir=Path(tmpdir))
config.settings_path.write_text(
json.dumps(
{
"embedding": {
"backend": "fastembed",
"model": "multilingual",
"use_gpu": False,
},
"llm": {
"enabled": True,
"tool": "gemini",
"timeout_ms": 1234,
"batch_size": 7,
},
}
),
encoding="utf-8",
)
with caplog.at_level(logging.WARNING):
config.load_settings()
records = [r for r in caplog.records if r.name == "codexlens.config"]
assert not records
assert config.embedding_backend == "fastembed"
assert config.embedding_model == "multilingual"
assert config.embedding_use_gpu is False
assert config.llm_enabled is True
assert config.llm_tool == "gemini"
assert config.llm_timeout_ms == 1234
assert config.llm_batch_size == 7
def test_load_settings_logs_warning_on_invalid_embedding_backend(self, caplog):
"""Invalid embedding backend should trigger warning log and keep default."""
with tempfile.TemporaryDirectory() as tmpdir:
config = Config(data_dir=Path(tmpdir))
default_backend = config.embedding_backend
config.settings_path.write_text(
json.dumps({"embedding": {"backend": "invalid-backend"}}),
encoding="utf-8",
)
with caplog.at_level(logging.WARNING):
config.load_settings()
records = [r for r in caplog.records if r.name == "codexlens.config"]
assert any("Invalid embedding backend in" in r.message for r in records)
assert config.embedding_backend == default_backend
class TestWorkspaceConfig: class TestWorkspaceConfig:
"""Tests for WorkspaceConfig class.""" """Tests for WorkspaceConfig class."""