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

View File

@@ -1,5 +1,8 @@
"""Tests for CodexLens configuration system."""
import builtins
import json
import logging
import os
import tempfile
from pathlib import Path
@@ -224,6 +227,99 @@ class TestConfig:
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:
"""Tests for WorkspaceConfig class."""