Skip to content

Conversation

@hoang-lam-thai
Copy link
Collaborator

@hoang-lam-thai hoang-lam-thai commented Nov 26, 2025

Pull Request

Summary

The --log-level DEBUG CLI option is ignored when logging has already been initialized

Type of change

  • Feature
  • Bug fix
  • Docs update
  • Chore

Docs Impact (required for any code or docs changes)

  • Does this change require docs updates? If yes, list pages:
  • Have you updated or added pages under docs/?
  • Did you build the site and run the link checker? (python website/build.py + python website/check_links.py)
  • Did you avoid banned placeholders? (no "TBD/coming soon")

Testing

Run the command below and observe the debug mode:

python.exe -m qdrant_loader.main ingest --workspace . --log-level DEBUG

Checklist

  • Tests pass (pytest -v)
  • Linting passes (make lint / make format)
  • Documentation updated (if applicable)

Summary by CodeRabbit

  • New Features
    • Runtime log-level reconfiguration: you can now change the logging level at runtime and have that level applied consistently across initialization, configuration, and ingest flows.
  • Improvements
    • Validation for log level values with clearer errors for invalid levels.
    • Reconfiguration preserves existing handlers and aligns structured logging output with the selected level.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Walkthrough

Adds an optional level parameter to LoggingConfig.reconfigure() so callers can set log level at runtime; the method now validates and applies the level to the stdlib root logger and reconfigures structlog. CLI command callers were updated to pass level=log_level during reconfiguration.

Changes

Cohort / File(s) Change Summary
Core Logging Configuration
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py
LoggingConfig.reconfigure() signature updated to `def reconfigure(cls, *, file: str
CLI Command Updates
packages/qdrant-loader/src/qdrant_loader/cli/commands/config.py, .../config_cmd.py, .../ingest_cmd.py, .../init_cmd.py
Calls to LoggingConfig.reconfigure() updated to include level=log_level alongside file=log_file where reconfiguration occurs; fallback/setup branches unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect logging.py changes for correct numeric level resolution, error handling, and structlog configuration alignment.
  • Verify _current_config state mutation is correct and thread-safe in context.
  • Confirm every CLI call site passes the intended log_level and that fallback branches remain correct.
  • Spot-check file handler preservation/replacement logic to ensure no duplicate handlers or handler leaks.

Possibly related issues

Poem

🐰 I hopped through code at break of dawn,
Updated levels to whisper and brawn,
CLI calls now pass the tune,
Logs hum bright beneath the moon,
A tiny rabbit cheers — hop on! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: enabling log level reconfiguration at runtime, which directly addresses issue #58.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/58

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (1)

1-435: Module size exceeds configured limit (435 lines > 400) and breaks the pipeline

The test/coverage pipeline is currently failing with:

core module logging.py has 435 lines > 400

The new reconfigure implementation pushed this file over the module-size threshold. To get the checks passing again, you’ll need to reduce the line count here, e.g.:

  • Extract helper classes and functions such as QdrantVersionFilter, ApplicationFilter, RedactionFilter, CleanFormatter, and perhaps _redact_processor into a dedicated module (e.g., logging_filters.py) and import them here, or
  • Factor some of the duplicated “choose renderer + configure structlog” logic into a shared private helper in another module, if that meaningfully reduces lines.

Without trimming this file below the limit (<= 400 lines) the CI will keep failing.

🧹 Nitpick comments (2)
packages/qdrant-loader/src/qdrant_loader/cli/commands/config_cmd.py (1)

23-27: Propagating log_level into reconfigure looks correct

Passing level=log_level here cleanly fixes the “re-invocation ignores CLI log level” bug while preserving the existing fallback path for older cores. Also consider updating the CLI/docs to clarify that --log-level is now honored even when logging was previously initialized.

packages/qdrant-loader/src/qdrant_loader/cli/commands/init_cmd.py (1)

42-56: Consider also passing level in the first reconfigure for consistency

The new LoggingConfig.reconfigure(file=log_file, level=log_level) at line 91 ensures the workspace-aware logger respects the CLI log level, which is great.

However, in the earlier block:

if getattr(LoggingConfig, "reconfigure", None):
    if getattr(LoggingConfig, "_initialized", False):
        LoggingConfig.reconfigure(file="qdrant-loader.log")  # no level here
    else:
        LoggingConfig.setup(level=log_level, format="console", file="qdrant-loader.log")

if logging was already initialized before this command, logs emitted between logger = LoggingConfig.get_logger(__name__) and the later reconfigure may still use the old level. For full consistency with the other commands and with the second reconfigure call, you may want:

-        if getattr(LoggingConfig, "_initialized", False):  # type: ignore[attr-defined]
-            LoggingConfig.reconfigure(file="qdrant-loader.log")  # type: ignore[attr-defined]
+        if getattr(LoggingConfig, "_initialized", False):  # type: ignore[attr-defined]
+            LoggingConfig.reconfigure(file="qdrant-loader.log", level=log_level)  # type: ignore[attr-defined]

Also applies to: 84-92

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daf39b8 and 9894dc0.

📒 Files selected for processing (5)
  • packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (2 hunks)
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/config.py (1 hunks)
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/config_cmd.py (1 hunks)
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/ingest_cmd.py (1 hunks)
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/init_cmd.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/back-end-dev.mdc)

**/*.py: Use Python 3.x for back-end implementation and code examples
When building LLM/AI workflows, prefer and follow best practices of PydanticAI or LangChain
Use Qdrant as the vector database with performant indexing and query patterns
Design and implement RESTful endpoints with clear resource modeling and proper status codes
Implement authentication/authorization using OAuth2 or JWT; follow secure session and token handling

Files:

  • packages/qdrant-loader/src/qdrant_loader/cli/commands/config.py
  • packages/qdrant-loader-core/src/qdrant_loader_core/logging.py
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/init_cmd.py
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/ingest_cmd.py
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/config_cmd.py
🧬 Code graph analysis (5)
packages/qdrant-loader/src/qdrant_loader/cli/commands/config.py (1)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (2)
  • LoggingConfig (190-435)
  • reconfigure (360-435)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (1)
packages/qdrant-loader-mcp-server/src/qdrant_loader_mcp_server/utils/logging.py (1)
  • reconfigure (135-162)
packages/qdrant-loader/src/qdrant_loader/cli/commands/init_cmd.py (2)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (2)
  • LoggingConfig (190-435)
  • reconfigure (360-435)
packages/qdrant-loader-mcp-server/src/qdrant_loader_mcp_server/utils/logging.py (2)
  • LoggingConfig (55-162)
  • reconfigure (135-162)
packages/qdrant-loader/src/qdrant_loader/cli/commands/ingest_cmd.py (2)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (1)
  • reconfigure (360-435)
packages/qdrant-loader-mcp-server/src/qdrant_loader_mcp_server/utils/logging.py (1)
  • reconfigure (135-162)
packages/qdrant-loader/src/qdrant_loader/cli/commands/config_cmd.py (2)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (2)
  • LoggingConfig (190-435)
  • reconfigure (360-435)
packages/qdrant-loader-mcp-server/src/qdrant_loader_mcp_server/utils/logging.py (2)
  • LoggingConfig (55-162)
  • reconfigure (135-162)
🪛 GitHub Actions: Test and Coverage
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py

[error] 1-435: Module size check failed: core module 'logging.py' has 435 lines > 400. Command failed: pytest tests/ --cov=src --cov-report=xml:../../coverage-core.xml --cov-report=html:../../htmlcov-core -v

🔇 Additional comments (3)
packages/qdrant-loader/src/qdrant_loader/cli/commands/ingest_cmd.py (1)

52-57: Ingest now correctly reuses logging with updated level

Using LoggingConfig.reconfigure(file=log_file, level=log_level) in the already-initialized branch aligns with the new core API and ensures --log-level is respected on re-run without disturbing the legacy fallback path.

packages/qdrant-loader/src/qdrant_loader/cli/commands/config.py (1)

39-55: Config helper now correctly reconfigures with the requested log level

Adding level=log_level to the LoggingConfig.reconfigure call in the initialized path makes this helper consistent with the other CLI commands and solves the “log level ignored after prior setup” issue while keeping the compatibility branch intact.

packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (1)

360-409: reconfigure(file=..., level=...) implementation looks sound for CLI use

The updated LoggingConfig.reconfigure:

  • Accepts an optional level and correctly maps it to numeric_level, updating the root logger and structlog wrapper while preserving existing processors and format selection from _current_config.
  • Raises a clear ValueError for invalid level values, mirroring the behavior of setup().
  • Keeps the existing file-handler replacement semantics intact and updates _current_config consistently with the new (level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console) tuple shape.

Given current call sites always pass both file and level, this should behave as intended and fix the runtime log-level reconfiguration bug without breaking older call sites that pass only file.

If you want extra safety, you could add a small regression test that:

  • Calls LoggingConfig.setup(level="INFO", ...),
  • Emits a DEBUG log (and asserts it’s filtered),
  • Calls LoggingConfig.reconfigure(level="DEBUG", file=...),
  • Emits a DEBUG log and asserts it’s now visible, using structlog’s configured wrapper.

Also applies to: 431-435

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (1)

1-435: Pipeline failure: File exceeds 400-line limit (435 lines).

The pipeline reports this module exceeds the size limit. Consider extracting the RedactionFilter class and _redact_processor function into a separate redaction.py module to reduce the file size.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9894dc0 and 08369cb.

📒 Files selected for processing (2)
  • packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (2 hunks)
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/init_cmd.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/qdrant-loader/src/qdrant_loader/cli/commands/init_cmd.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/back-end-dev.mdc)

**/*.py: Use Python 3.x for back-end implementation and code examples
When building LLM/AI workflows, prefer and follow best practices of PydanticAI or LangChain
Use Qdrant as the vector database with performant indexing and query patterns
Design and implement RESTful endpoints with clear resource modeling and proper status codes
Implement authentication/authorization using OAuth2 or JWT; follow secure session and token handling

Files:

  • packages/qdrant-loader-core/src/qdrant_loader_core/logging.py
🧬 Code graph analysis (1)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (1)
packages/qdrant-loader-mcp-server/src/qdrant_loader_mcp_server/utils/logging.py (1)
  • reconfigure (135-162)
🪛 GitHub Actions: Test and Coverage
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py

[error] 1-435: Core module 'logging.py' exceeds size limit: 435 lines > 400.

🔇 Additional comments (2)
packages/qdrant-loader-core/src/qdrant_loader_core/logging.py (2)

360-369: LGTM on method signature and docstring.

The optional level parameter with clear documentation is a clean approach to fix the runtime reconfiguration issue.


431-435: LGTM on config tuple update.

The unpacking here correctly handles all 6 tuple elements, and the new level is properly persisted using level.upper() if level is not None else old_level.

Comment on lines +373 to +409
if level is not None:
try:
numeric_level = getattr(logging, level.upper())
root_logger.setLevel(numeric_level)

# Update structlog wrapper to use new level
if cls._current_config is not None:
_, fmt, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config

# Choose timestamp format and final renderer
if clean_output and fmt == "console":
ts_fmt = "%H:%M:%S"
final_renderer = structlog.dev.ConsoleRenderer(colors=True)
else:
ts_fmt = "iso"
final_renderer = (
structlog.processors.JSONRenderer()
if fmt == "json"
else structlog.dev.ConsoleRenderer(colors=True)
)

# Reconfigure structlog with new level
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt=ts_fmt),
_redact_processor,
final_renderer,
],
wrapper_class=structlog.make_filtering_bound_logger(numeric_level),
logger_factory=LoggerFactory(),
cache_logger_on_first_use=False,
)
except AttributeError:
raise ValueError(f"Invalid log level: {level}") from None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's examine the logging.py file structure and find the unpacking bug context
wc -l packages/qdrant-loader-core/src/qdrant_loader_core/logging.py

Repository: martin-papy/qdrant-loader

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Read the relevant section of the file including context before line 373
sed -n '350,420p' packages/qdrant-loader-core/src/qdrant_loader_core/logging.py

Repository: martin-papy/qdrant-loader

Length of output: 3106


🏁 Script executed:

#!/bin/bash
# Search for where _current_config is set to understand the tuple structure
rg -n "_current_config\s*=" packages/qdrant-loader-core/src/qdrant_loader_core/logging.py -B2 -A2

Repository: martin-papy/qdrant-loader

Length of output: 653


🏁 Script executed:

#!/bin/bash
# Search for reconfigure calls and level parameter passing
rg -n "reconfigure\(" packages/qdrant-loader-core/src/qdrant_loader_core/logging.py -C3

Repository: martin-papy/qdrant-loader

Length of output: 405


🏁 Script executed:

#!/bin/bash
# Let's get the exact unpacking at line 382 and surrounding context
sed -n '375,390p' packages/qdrant-loader-core/src/qdrant_loader_core/logging.py | cat -n

Repository: martin-papy/qdrant-loader

Length of output: 984


🏁 Script executed:

#!/bin/bash
# Check git history for recent changes to this unpacking
cd packages/qdrant-loader-core/src/qdrant_loader_core && \
git log --oneline -20 logging.py

Repository: martin-papy/qdrant-loader

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Look at git diff to see what changed in the unpacking
cd packages/qdrant-loader-core/src/qdrant_loader_core && \
git diff HEAD~5 logging.py | grep -A10 -B10 "_current_config" | head -80

Repository: martin-papy/qdrant-loader

Length of output: 255


🏁 Script executed:

#!/bin/bash
# Check the specific git commit that supposedly fixed the issue
cd packages/qdrant-loader-core/src/qdrant_loader_core && \
git show 08369cb --stat

Repository: martin-papy/qdrant-loader

Length of output: 48060


🏁 Script executed:

#!/bin/bash
# Look at the changes in that commit
cd packages/qdrant-loader-core/src/qdrant_loader_core && \
git show 08369cb | head -200

Repository: martin-papy/qdrant-loader

Length of output: 13428


🏁 Script executed:

#!/bin/bash
# Verify current state - check if unpacking matches tuple size
cd packages/qdrant-loader-core/src/qdrant_loader_core && \
python3 << 'EOF'
# Count elements in tuple structure
lines_351 = "current_tuple = (level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console)"
lines_435 = "old_level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config"
lines_382 = "_, fmt, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config"

tuple_elements_351 = 6  # level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console
unpack_elements_382 = 5  # _, fmt, clean_output, suppress_qdrant_warnings, disable_console
unpack_elements_435 = 6  # old_level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console

print(f"Tuple elements (line 351): {tuple_elements_351}")
print(f"Unpack elements (line 382): {unpack_elements_382}")
print(f"Unpack elements (line 435): {unpack_elements_435}")
print(f"\nLine 382 MISMATCH: {tuple_elements_351} != {unpack_elements_382}")
EOF

Repository: martin-papy/qdrant-loader

Length of output: 155


🏁 Script executed:

#!/bin/bash
# Let's verify the exact line and surrounding context one more time to be absolutely certain
sed -n '375,390p' packages/qdrant-loader-core/src/qdrant_loader_core/logging.py | cat -n

Repository: martin-papy/qdrant-loader

Length of output: 984


The tuple unpacking at line 382 is incomplete and will cause a ValueError at runtime.

The _current_config tuple contains 6 elements: (level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console). Line 382 attempts to unpack only 5 elements, omitting the file element:

_, fmt, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config

This will fail with ValueError: not enough values to unpack (expected 6, got 5) when level is not None. Compare with line 435 in the same method, which correctly unpacks all 6 elements:

old_level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config

Required fix: Update line 382 to include file in the unpacking:

_, fmt, file, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config
🤖 Prompt for AI Agents
In packages/qdrant-loader-core/src/qdrant_loader_core/logging.py around lines
373 to 409, the tuple unpacking for cls._current_config on line ~382 omits the
`file` element causing a ValueError; update the unpacking to include `file` so
it reads: _, fmt, file, clean_output, suppress_qdrant_warnings, disable_console
= cls._current_config, preserving the rest of the reconfiguration logic.

Comment on lines +379 to +392
if cls._current_config is not None:
_, fmt, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config

# Choose timestamp format and final renderer
if clean_output and fmt == "console":
ts_fmt = "%H:%M:%S"
final_renderer = structlog.dev.ConsoleRenderer(colors=True)
else:
ts_fmt = "iso"
final_renderer = (
structlog.processors.JSONRenderer()
if fmt == "json"
else structlog.dev.ConsoleRenderer(colors=True)
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Tuple unpacking mismatch will cause ValueError at runtime.

The _current_config tuple has 6 elements (level, fmt, file, clean_output, suppress_qdrant_warnings, disable_console), but line 380 unpacks into only 5 variables. This will raise ValueError: too many values to unpack.

Additionally, even if Python didn't error, the variable bindings would be incorrect—clean_output would receive the file value, etc.

Apply this diff to fix the unpacking:

                 if cls._current_config is not None:
-                    _, fmt, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config
+                    _, fmt, _, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if cls._current_config is not None:
_, fmt, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config
# Choose timestamp format and final renderer
if clean_output and fmt == "console":
ts_fmt = "%H:%M:%S"
final_renderer = structlog.dev.ConsoleRenderer(colors=True)
else:
ts_fmt = "iso"
final_renderer = (
structlog.processors.JSONRenderer()
if fmt == "json"
else structlog.dev.ConsoleRenderer(colors=True)
)
if cls._current_config is not None:
_, fmt, _, clean_output, suppress_qdrant_warnings, disable_console = cls._current_config
# Choose timestamp format and final renderer
if clean_output and fmt == "console":
ts_fmt = "%H:%M:%S"
final_renderer = structlog.dev.ConsoleRenderer(colors=True)
else:
ts_fmt = "iso"
final_renderer = (
structlog.processors.JSONRenderer()
if fmt == "json"
else structlog.dev.ConsoleRenderer(colors=True)
)
🤖 Prompt for AI Agents
In packages/qdrant-loader-core/src/qdrant_loader_core/logging.py around lines
379 to 392, the code unpacks cls._current_config into only five variables
causing a ValueError and incorrect bindings; update the unpacking to six
variables matching the stored tuple (level, fmt, file, clean_output,
suppress_qdrant_warnings, disable_console) so subsequent uses of fmt and
clean_output refer to the correct values, and ensure any later references to the
file, suppress_qdrant_warnings, or disable_console variables use these newly
unpacked names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants