Skip to content

LCORE- Unify logging configuration and setup#1703

Open
samdoran wants to merge 21 commits into
lightspeed-core:mainfrom
samdoran:use-existing-logger
Open

LCORE- Unify logging configuration and setup#1703
samdoran wants to merge 21 commits into
lightspeed-core:mainfrom
samdoran:use-existing-logger

Conversation

@samdoran
Copy link
Copy Markdown
Contributor

@samdoran samdoran commented May 8, 2026

Description

Major overhaul of the logging system:

  • Unify the logging configuration from lightspeed-stack and uvicorn
  • Change the time format to include milliseconds
  • Pass the unified configuration into uvicorn so that all application logging works consistently
  • Call setup_logging() early on so that logging is setup once and cache the result.
  • Manually set the root logger name of lightspeed_stack so that the logging configuration is applied correctly.

The end result of these changes is that calling get_logger(__name__) results in a logger instance where logger.info() and other methods display log messages as expected and formatted consistently.

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement
  • Benchmarks improvement

Tools used to create PR

Identify any AI code assistants used in this PR (for transparency and review context)

  • Assisted-by: Claude
  • Written by: me

Related Tickets & Documents

None

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

To run unit tests: pytest tests/unit/test_log.py
To test logging configuration without Rich logging: LIGHTSPEED_STACK_DISABLE_RICH_HANDLER=1 lightspeed-stack or python src/lightspeed_stack.py
To test logging configuration using the default format string: lightspeed-stack or python src/lightspeed_stack.py

Summary by CodeRabbit

  • Refactor

    • Centralized logging initialization for consistent behavior across the app and server runner.
    • Standardized default logger naming for clearer log source identification.
    • Plain-text log layout adjusted for improved readability and ordering.
    • Handler selection now respects terminal detection and an environment toggle; server runner forwards the logging configuration.
  • Tests

    • Unit tests updated to initialize/reset the shared logging setup and use structured log capture for assertions.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

📝 Walkthrough

Walkthrough

This PR centralizes logging into a cached setup_logging() that merges custom config into Uvicorn's LOGGING_CONFIG, adds DEFAULT_LOGGER_NAME and a new plain-text format, invokes setup during module import and in the Uvicorn runner, and updates tests to exercise and reset the new flow.

Changes

Logging Setup Refactoring

Layer / File(s) Summary
Logging Constants
src/constants.py
Adds DEFAULT_LOGGER_NAME and updates DEFAULT_LOG_FORMAT template.
Core logging implementation
src/log.py
Adds cached setup_logging() that selects handler by TTY/env, deep-merges into uvicorn.config.LOGGING_CONFIG, applies logging.config.dictConfig, removes create_log_handler(), and simplifies get_logger() to return namespaced loggers; adds millisecond time formatter.
Entrypoint & runner integration
src/lightspeed_stack.py, src/runners/uvicorn.py
Entrypoint calls setup_logging() at import. start_uvicorn gains optional log_config parameter and uses setup_logging() when none provided; passes log_config into uvicorn.run().
Tests & fixtures
tests/unit/..., tests/unit/conftest.py
Adds autouse fixture to clear setup_logging() cache, refactors tests to use caplog and target namespaced loggers, asserts numeric effective levels, and removes tests for the old create_log_handler behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main objective of the PR: unifying logging configuration and setup across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
Contributor

@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: 7

Caution

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

⚠️ Outside diff range comments (1)
src/runners/uvicorn.py (1)

17-25: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Docstring doesn't document the new log_config parameter

🛠 Proposed fix
     """Start the Uvicorn server using the provided service configuration.

     Parameters:
     ----------
         configuration (ServiceConfiguration): Configuration providing host,
         port, workers, and `tls_config` (including `tls_key_path`,
         `tls_certificate_path`, and `tls_key_password`). TLS fields may be None
         and will be forwarded to uvicorn.run as provided.
+        log_config (dict | None): Logging configuration dict passed to uvicorn.run.
+        When None, the centralized setup_logging() result is used.
     """

As per coding guidelines, all functions must "include descriptive docstrings" covering all parameters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runners/uvicorn.py` around lines 17 - 25, The docstring for the
Uvicorn-starting function (start_uvicorn in src/runners/uvicorn.py) is missing
documentation for the new log_config parameter; update the function docstring to
list and describe log_config (type: optional dict or logging config path,
default/behavior: forwarded to uvicorn.run to configure logging) alongside the
existing parameters so callers know what to pass and how it is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/constants.py`:
- Line 228: DEFAULT_LOGGER_NAME is missing the Final[str] annotation; update the
constant declaration for DEFAULT_LOGGER_NAME to be annotated with Final[str]
(e.g., DEFAULT_LOGGER_NAME: Final[str] = "lightspeed_stack") and ensure Final is
imported from typing at the top of src/constants.py (add "from typing import
Final" if it's not already present) so the constant is type-locked like the
others.

In `@src/log.py`:
- Line 1: The file src/log.py fails Black formatting; run the project formatter
and commit the changes: execute "uv tool run black src/log.py" (or "uv tool run
black src tests") to reformat the module (src.log) and then stage and commit the
updated file so the CI Black check passes.
- Around line 59-70: The logger helper uses a DEFAULT_LOGGER_NAME prefix to work
around inconsistent __name__ values across different entrypoints (see get_logger
and DEFAULT_LOGGER_NAME); open a tracking issue to remove that workaround by
standardizing the package/entrypoint layout so __name__ always yields the
expected root, document the change, and include a migration plan: update
get_logger to use __name__ (remove the DEFAULT_LOGGER_NAME concatenation),
refactor any callers that relied on the prefixed logger names (adjust tests and
configuration), and add a small integration test that verifies the root logger
name is consistent when run via all supported entrypoints.
- Around line 107-117: The setup_logging() function currently calls
deep_update(uvicorn.config.LOGGING_CONFIG, logging_conf) then mutates
merged_config which aliases uvicorn.config.LOGGING_CONFIG; instead construct all
conditional changes (the "rich" handler branch and the access/default formatter
entries: the handlers for "uvicorn" and "uvicorn.access" and the
formatters["access"] and formatters["default"]["fmt"/"datefmt"]) inside
logging_conf before calling deep_update so the single deep_update call merges
everything and merged_config is never mutated afterwards; update the code paths
that reference handler, merged_config and logging_conf accordingly so no
post-merge assignments (e.g., merged_config["formatters"][...] = ...) remain.
- Line 89: The Rich handler configuration uses "log_time_format" set to
"%Y-%m-%d %H:%M:%S.%f" which emits 6-digit microseconds instead of the intended
3-digit milliseconds; update the Rich-based logging path to remove the "%f"
subsecond directive (or omit log_time_format entirely) and rely on RichHandler's
own millisecond column via show_time=True, ensuring consistency with
DEFAULT_LOG_FORMAT which uses "%(msecs)03d"; modify the code that sets
log_time_format (symbol: log_time_format) and the Rich handler instantiation
(symbol: RichHandler, show_time) so the Rich path does not output 6-digit
microseconds.
- Line 11: The import of pydantic.v1.utils.deep_update in src/log.py uses an
internal v1-compat API — remove that import, add a small local helper function
named _deep_update(base: dict, override: dict) that recursively merges override
into base (preserving dict semantics), and replace all usages of
deep_update(...) with _deep_update(...); update any function or module
references in this file that call deep_update to call _deep_update instead so
there is no dependency on pydantic internals.
- Around line 73-74: Add a clear docstring to the public function
setup_logging() that explains what the function does (e.g., configures and
returns the logging configuration dict, applies any global logging settings or
handlers) and any side effects (such as calling logging.config.dictConfig or
mutating global logger state); ensure the docstring appears immediately below
the def setup_logging() line and follows project docstring style so pydocstyle
D103 is satisfied.

---

Outside diff comments:
In `@src/runners/uvicorn.py`:
- Around line 17-25: The docstring for the Uvicorn-starting function
(start_uvicorn in src/runners/uvicorn.py) is missing documentation for the new
log_config parameter; update the function docstring to list and describe
log_config (type: optional dict or logging config path, default/behavior:
forwarded to uvicorn.run to configure logging) alongside the existing parameters
so callers know what to pass and how it is used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1c2b1d91-2f32-49b8-ae1e-1cede9a1feb3

📥 Commits

Reviewing files that changed from the base of the PR and between 4feeaed and 113ef13.

📒 Files selected for processing (5)
  • src/constants.py
  • src/lightspeed_stack.py
  • src/log.py
  • src/runners/uvicorn.py
  • tests/unit/test_log.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: unit_tests (3.12)
  • GitHub Check: build-pr
  • GitHub Check: Pylinter
  • GitHub Check: E2E: library mode / ci / group 3
  • GitHub Check: E2E: server mode / ci / group 2
  • GitHub Check: E2E: library mode / ci / group 2
  • GitHub Check: E2E: server mode / ci / group 3
  • GitHub Check: E2E: server mode / ci / group 1
  • GitHub Check: E2E: library mode / ci / group 1
  • GitHub Check: E2E Tests for Lightspeed Evaluation job
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use absolute imports for internal modules: from authentication import get_auth_dependency
Llama Stack imports: Use from llama_stack_client import AsyncLlamaStackClient
Check constants.py for shared constants before defining new ones
All modules must start with descriptive docstrings explaining purpose
Use logger = get_logger(__name__) from log.py for module logging
All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings
Use snake_case with descriptive, action-oriented names for functions (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying function parameters
Use async def for I/O operations and external API calls
Use standard log levels with clear purposes: debug() for diagnostic info, info() for program execution, warning() for unexpected events, error() for serious problems
All classes must have descriptive docstrings explaining purpose and use PascalCase with standard suffixes: Configuration, Error/Exception, Resolver, Interface
Abstract classes must use ABC with @abstractmethod decorators
Follow Google Python docstring conventions with required sections: Parameters, Returns, Raises, and Attributes for classes

Files:

  • src/constants.py
  • src/lightspeed_stack.py
  • src/runners/uvicorn.py
  • src/log.py
src/constants.py

📄 CodeRabbit inference engine (AGENTS.md)

Use constants.py for shared constants with descriptive comments and type hints using Final[type]

Files:

  • src/constants.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Use pytest for all unit and integration tests; do not use unittest
Use pytest.mark.asyncio marker for async tests

Files:

  • tests/unit/test_log.py
🪛 GitHub Actions: Black / 0_black.txt
src/log.py

[error] 1-1: Black formatting check failed (uv tool run black --check src tests). File would be reformatted. Run 'uv tool run black src tests' (or 'black --write') to fix.

🪛 GitHub Actions: Black / black
src/log.py

[error] 1-1: Black --check failed: would reformat /home/runner/work/lightspeed-stack/lightspeed-stack/src/log.py (1 file would be reformatted). Run 'uv tool run black src/log.py' (or 'uv tool run black --write src tests') to apply formatting.

🪛 GitHub Actions: Pydocstyle / 0_pydocstyle.txt
src/log.py

[error] 74-74: pydocstyle failed (D103): Missing docstring in public function setup_logging.

🪛 GitHub Actions: Pydocstyle / pydocstyle
src/log.py

[error] 74-74: pydocstyle (uv tool run pydocstyle -v src tests) reported: D103: Missing docstring in public function setup_logging.

🔇 Additional comments (2)
src/lightspeed_stack.py (1)

13-19: LGTM — logging initialization order is correct

setup_logging() is called before get_logger(__name__), ensuring dictConfig is applied before the logger hierarchy is first accessed.

tests/unit/test_log.py (1)

14-16: The fixture is working correctly. deep_update from pydantic.v1.utils creates a new merged dictionary rather than mutating uvicorn.config.LOGGING_CONFIG in-place, so setup_logging() does not leave persistent global state mutations across tests. Clearing the @lru_cache ensures the function body re-executes for each test, which is the appropriate behavior since tests modify environment variables via monkeypatch between runs.

Comment thread src/constants.py Outdated
Comment thread src/log.py
Comment thread src/log.py
Comment thread src/log.py
Comment thread src/log.py
Comment thread src/log.py Outdated
Comment thread src/log.py Outdated
@samdoran samdoran force-pushed the use-existing-logger branch from 113ef13 to b321e1d Compare May 11, 2026 20:31
Copy link
Copy Markdown
Contributor

@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.

Caution

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

⚠️ Outside diff range comments (1)
src/runners/uvicorn.py (1)

13-25: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update docstring to document the new log_config parameter.

The function signature was updated to accept log_config: dict | None = None, but the docstring does not document this parameter.

📝 Proposed fix
     """Start the Uvicorn server using the provided service configuration.

     Parameters:
     ----------
         configuration (ServiceConfiguration): Configuration providing host,
         port, workers, and `tls_config` (including `tls_key_path`,
         `tls_certificate_path`, and `tls_key_password`). TLS fields may be None
         and will be forwarded to uvicorn.run as provided.
+        log_config (dict | None): Optional logging configuration dict to pass
+            to uvicorn.run. If None, setup_logging() is called to generate the
+            unified configuration.
     """

As per coding guidelines, src/**/*.py requires "All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runners/uvicorn.py` around lines 13 - 25, The docstring for start_uvicorn
is missing documentation for the new log_config parameter; update the function's
docstring to add a "Parameters" entry for log_config (type dict | None)
describing that it supplies an optional uvicorn logging configuration passed to
uvicorn.run and that None uses default logging behavior, and ensure the return
type sentence remains accurate for the function signature of start_uvicorn.
♻️ Duplicate comments (4)
src/log.py (4)

11-11: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

pydantic.v1.utils.deep_update is an internal v1-compat shim — replace with a self-contained helper.

pydantic.v1.utils is part of the v1 compatibility layer bundled in pydantic v2, not a public API. A 5-line replacement eliminates the dependency on pydantic internals.

♻️ Proposed replacement

Remove the pydantic import and add a local helper:

-from pydantic.v1.utils import deep_update
def _deep_update(base: dict, override: dict) -> dict:
    """Recursively merge override into base, returning a new dict."""
    result = base.copy()
    for key, value in override.items():
        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
            result[key] = _deep_update(result[key], value)
        else:
            result[key] = value
    return result

Then replace all calls to deep_update(...) with _deep_update(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` at line 11, The import from pydantic.v1.utils using deep_update
is relying on an internal v1-compat API; remove the line importing deep_update
and add a small local helper named _deep_update that recursively merges two
dicts (signature _deep_update(base: dict, override: dict) -> dict), then replace
all usages of deep_update(...) in this module with _deep_update(...); ensure the
helper returns a new dict and performs recursive merging when both values are
dicts to preserve current behavior of deep_update.

108-119: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

setup_logging() silently mutates uvicorn.config.LOGGING_CONFIG — corrupts global state on cache replay.

deep_update(uvicorn.config.LOGGING_CONFIG, logging_conf) performs only a shallow copy at the top level. Because logging_conf contains no "formatters" key, merged_config["formatters"] is the same dict object as uvicorn.config.LOGGING_CONFIG["formatters"]. The subsequent assignments on lines 114–119 therefore permanently mutate uvicorn's module-level global. Similarly, line 111-112 mutate the uvicorn logger dicts.

In tests this causes silent pollution: cache_clear() re-runs the function body against an already-mutated uvicorn.config.LOGGING_CONFIG.

🛠 Proposed fix

Build all conditional configuration inside logging_conf before the single deep_update call:

     logging_conf = {
         "version": 1,
         "disable_existing_loggers": False,
         "handlers": {
             "rich": {
                 "()": "rich.logging.RichHandler",
                 "show_time": True,
-                "log_time_format": "%Y-%m-%d %H:%M:%S.%f",
+                "log_time_format": "%Y-%m-%d %H:%M:%S",
                 "level": log_level,
             },
         },
         "loggers": {
             DEFAULT_LOGGER_NAME: {
                 "handlers": [handler],
                 "level": log_level,
                 "propagate": False,
             },
             "llama_stack_client": {
                 "handlers": [handler],
                 "level": log_level,
                 "propagate": False,
             },
         },
     }

-    merged_config = deep_update(uvicorn.config.LOGGING_CONFIG, logging_conf)
-
     if handler == "rich":
-        merged_config["loggers"]["uvicorn"]["handlers"] = [handler]
-        merged_config["loggers"]["uvicorn.access"]["handlers"] = [handler]
+        logging_conf["loggers"]["uvicorn"] = {"handlers": ["rich"]}
+        logging_conf["loggers"]["uvicorn.access"] = {"handlers": ["rich"]}
     else:
-        merged_config["formatters"]["access"]["fmt"] = (
-            "%(asctime)s.%(msecs)03d %(levelprefix)s "
-            '%(client_addr)s - "%(request_line)s" %(status_code)s'
-        )
-        merged_config["formatters"]["default"]["fmt"] = DEFAULT_LOG_FORMAT
-        merged_config["formatters"]["default"]["datefmt"] = "%Y-%m-%d %H:%M"
+        logging_conf["formatters"] = {
+            "access": {
+                "fmt": '%(asctime)s.%(msecs)03d %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s',
+            },
+            "default": {
+                "fmt": DEFAULT_LOG_FORMAT,
+                "datefmt": "%Y-%m-%d %H:%M",
+            },
+        }
+
+    merged_config = deep_update(uvicorn.config.LOGGING_CONFIG, logging_conf)

     logging.config.dictConfig(merged_config)
     return merged_config
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` around lines 108 - 119, setup_logging currently calls
deep_update(uvicorn.config.LOGGING_CONFIG, logging_conf) then mutates
merged_config (and thus uvicorn.config.LOGGING_CONFIG) via assignments to
merged_config["formatters"] and merged_config["loggers"], corrupting global
state; fix by avoiding in-place mutation of uvicorn's global: either deepcopy
uvicorn.config.LOGGING_CONFIG before merging (use copy.deepcopy on
uvicorn.config.LOGGING_CONFIG into merged_config) or build all conditional keys
into logging_conf first (populate logging_conf["formatters"] and
logging_conf["loggers"] based on handler) and then call deep_update once; update
references to merged_config, deep_update, logging_conf and setup_logging
accordingly so no assignment mutates uvicorn.config.LOGGING_CONFIG.

90-90: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

log_time_format uses %f (microseconds, 6 digits) — PR goal was milliseconds.

The %f strftime directive expands to 6-digit microseconds (e.g., 123456). The non-Rich path correctly uses %(msecs)03d for 3-digit milliseconds in DEFAULT_LOG_FORMAT. The Rich handler will show 6-digit precision instead of the intended 3-digit millisecond granularity.

🛠 Proposed fix
-                "log_time_format": "%Y-%m-%d %H:%M:%S.%f",
+                "log_time_format": "%Y-%m-%d %H:%M:%S",

Rich's RichHandler renders its own millisecond column separately when show_time=True, so the sub-second portion in log_time_format is typically redundant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` at line 90, The log_time_format currently uses "%Y-%m-%d
%H:%M:%S.%f" which produces 6-digit microseconds instead of the intended 3-digit
milliseconds; remove the "%f" sub-second directive (e.g., use "%Y-%m-%d
%H:%M:%S") so RichHandler (with show_time=True) renders milliseconds itself and
the non-Rich path continues to use %(msecs)03d from DEFAULT_LOG_FORMAT; update
the log_time_format constant and ensure any RichHandler configuration does not
rely on "%f".

73-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

setup_logging() is missing a docstring.

The function lacks a docstring describing its behavior, side effects, and return value.

🛠 Proposed fix
 `@lru_cache`
 def setup_logging() -> dict[t.Any, t.Any]:
+    """Configure and return the merged logging configuration.
+
+    Selects a Rich or uvicorn default handler based on TTY detection and the
+    LIGHTSPEED_STACK_DISABLE_RICH_HANDLER environment variable, merges the
+    computed configuration into uvicorn's default logging config, applies it
+    via logging.config.dictConfig, and returns the merged configuration dict.
+    Result is cached; call setup_logging.cache_clear() to force re-initialization.
+
+    Returns:
+        dict: The merged logging configuration passed to dictConfig.
+    """

As per coding guidelines, src/**/*.py requires "All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` around lines 73 - 74, Add a descriptive docstring to
setup_logging() that explains what the function does (configures/initializes
logging), any side effects (registers handlers, configures root logger, uses
lru_cache to memoize), and what it returns (a dict of the logging
configuration). Place the docstring immediately under the `@lru_cache-decorated`
def setup_logging(...) and ensure it uses complete, clear language describing
parameters (none), return value (dict[t.Any, t.Any] or updated to modern typing
if you choose), and any important notes about caching/side effects.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/runners/uvicorn.py`:
- Around line 13-25: The docstring for start_uvicorn is missing documentation
for the new log_config parameter; update the function's docstring to add a
"Parameters" entry for log_config (type dict | None) describing that it supplies
an optional uvicorn logging configuration passed to uvicorn.run and that None
uses default logging behavior, and ensure the return type sentence remains
accurate for the function signature of start_uvicorn.

---

Duplicate comments:
In `@src/log.py`:
- Line 11: The import from pydantic.v1.utils using deep_update is relying on an
internal v1-compat API; remove the line importing deep_update and add a small
local helper named _deep_update that recursively merges two dicts (signature
_deep_update(base: dict, override: dict) -> dict), then replace all usages of
deep_update(...) in this module with _deep_update(...); ensure the helper
returns a new dict and performs recursive merging when both values are dicts to
preserve current behavior of deep_update.
- Around line 108-119: setup_logging currently calls
deep_update(uvicorn.config.LOGGING_CONFIG, logging_conf) then mutates
merged_config (and thus uvicorn.config.LOGGING_CONFIG) via assignments to
merged_config["formatters"] and merged_config["loggers"], corrupting global
state; fix by avoiding in-place mutation of uvicorn's global: either deepcopy
uvicorn.config.LOGGING_CONFIG before merging (use copy.deepcopy on
uvicorn.config.LOGGING_CONFIG into merged_config) or build all conditional keys
into logging_conf first (populate logging_conf["formatters"] and
logging_conf["loggers"] based on handler) and then call deep_update once; update
references to merged_config, deep_update, logging_conf and setup_logging
accordingly so no assignment mutates uvicorn.config.LOGGING_CONFIG.
- Line 90: The log_time_format currently uses "%Y-%m-%d %H:%M:%S.%f" which
produces 6-digit microseconds instead of the intended 3-digit milliseconds;
remove the "%f" sub-second directive (e.g., use "%Y-%m-%d %H:%M:%S") so
RichHandler (with show_time=True) renders milliseconds itself and the non-Rich
path continues to use %(msecs)03d from DEFAULT_LOG_FORMAT; update the
log_time_format constant and ensure any RichHandler configuration does not rely
on "%f".
- Around line 73-74: Add a descriptive docstring to setup_logging() that
explains what the function does (configures/initializes logging), any side
effects (registers handlers, configures root logger, uses lru_cache to memoize),
and what it returns (a dict of the logging configuration). Place the docstring
immediately under the `@lru_cache-decorated` def setup_logging(...) and ensure it
uses complete, clear language describing parameters (none), return value
(dict[t.Any, t.Any] or updated to modern typing if you choose), and any
important notes about caching/side effects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 69e249b9-b492-46f9-bcfd-45f3a03de2c9

📥 Commits

Reviewing files that changed from the base of the PR and between 113ef13 and b321e1d.

📒 Files selected for processing (5)
  • src/constants.py
  • src/lightspeed_stack.py
  • src/log.py
  • src/runners/uvicorn.py
  • tests/unit/test_log.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Pyright
  • GitHub Check: E2E: library mode / ci / group 1
  • GitHub Check: E2E: library mode / ci / group 3
  • GitHub Check: unit_tests (3.12)
  • GitHub Check: E2E: server mode / ci / group 2
  • GitHub Check: E2E: server mode / ci / group 3
  • GitHub Check: E2E: server mode / ci / group 1
  • GitHub Check: build-pr
  • GitHub Check: E2E: library mode / ci / group 2
  • GitHub Check: Pylinter
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use absolute imports for internal modules: from authentication import get_auth_dependency
Llama Stack imports: Use from llama_stack_client import AsyncLlamaStackClient
Check constants.py for shared constants before defining new ones
All modules must start with descriptive docstrings explaining purpose
Use logger = get_logger(__name__) from log.py for module logging
All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings
Use snake_case with descriptive, action-oriented names for functions (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying function parameters
Use async def for I/O operations and external API calls
Use standard log levels with clear purposes: debug() for diagnostic info, info() for program execution, warning() for unexpected events, error() for serious problems
All classes must have descriptive docstrings explaining purpose and use PascalCase with standard suffixes: Configuration, Error/Exception, Resolver, Interface
Abstract classes must use ABC with @abstractmethod decorators
Follow Google Python docstring conventions with required sections: Parameters, Returns, Raises, and Attributes for classes

Files:

  • src/lightspeed_stack.py
  • src/constants.py
  • src/runners/uvicorn.py
  • src/log.py
src/constants.py

📄 CodeRabbit inference engine (AGENTS.md)

Use constants.py for shared constants with descriptive comments and type hints using Final[type]

Files:

  • src/constants.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Use pytest for all unit and integration tests; do not use unittest
Use pytest.mark.asyncio marker for async tests

Files:

  • tests/unit/test_log.py
🔇 Additional comments (2)
src/constants.py (1)

228-228: ⚡ Quick win

DEFAULT_LOGGER_NAME is missing its Final[str] annotation.

Every other constant in this file uses Final[type]. This one does not, leaving it open to accidental reassignment.

🛠 Proposed fix
-DEFAULT_LOGGER_NAME = "lightspeed_stack"
+DEFAULT_LOGGER_NAME: Final[str] = "lightspeed_stack"

As per coding guidelines, src/constants.py requires "type hints using Final[type]" for shared constants.

src/log.py (1)

1-1: No Black formatting issues found.

The file complies with Black formatting standards. All lines are under 88 characters, imports are properly organized (stdlib, third-party, local), and spacing between top-level functions follows Black conventions (two blank lines). The file is ready for merge from a formatting perspective.

@samdoran samdoran changed the title Unify logging configuration and setup LCORE- Unify logging configuration and setup May 11, 2026
Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (3)
src/log.py (3)

13-13: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

pydantic.v1.utils.deep_update is an internal v1-compat shim — replace with a self-contained helper

This import uses an internal API from pydantic's v1 compatibility layer, creating unnecessary coupling for a simple utility function.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` at line 13, Replace the internal import of
pydantic.v1.utils.deep_update with a local helper: remove the line importing
deep_update and add a small self-contained deep_update function in src/log.py
that recursively merges dict-like structures (preserving nested dicts and
replacing non-dict values), then update all usages in this file to call the new
local deep_update helper (referenced by its function name deep_update) so the
module no longer depends on pydantic internals.

82-83: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Expand setup_logging() docstring to document side effects and caching.

The current docstring is too brief. It should explain that this function configures logging globally via dictConfig, is cached via lru_cache, and describe the returned merged configuration structure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` around lines 82 - 83, Update the setup_logging() docstring to
describe side effects and caching: state that setup_logging() globally
configures Python logging by calling dictConfig (applies side effects), is
decorated with lru_cache so repeated calls return the cached configuration, and
return value is the merged dict configuration (handlers, formatters, loggers,
level info) that was applied; mention any notable keys in the returned structure
and that callers receive the applied config rather than a fresh copy.

119-128: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider moving post-merge assignments into logging_conf before the deep_update call.

While deepcopy prevents mutating the global uvicorn config, constructing all conditional configuration inside logging_conf before calling deep_update would eliminate post-merge mutations entirely and make the merge operation clearer.

♻️ Proposed refactor
+    # Build conditional formatters/loggers before merge
     if handler == "rich":
-        merged_config = deep_update(deepcopy(uvicorn.config.LOGGING_CONFIG), logging_conf)
-        merged_config["loggers"]["uvicorn"]["handlers"] = [handler]
-        merged_config["loggers"]["uvicorn.access"]["handlers"] = [handler]
+        logging_conf["loggers"]["uvicorn"] = {"handlers": ["rich"]}
+        logging_conf["loggers"]["uvicorn.access"] = {"handlers": ["rich"]}
     else:
-        merged_config = deep_update(deepcopy(uvicorn.config.LOGGING_CONFIG), logging_conf)
-        merged_config["formatters"]["access"]["fmt"] = (
-            "%(asctime)s.%(msecs)03d %(levelprefix)s "
-            '%(client_addr)s - "%(request_line)s" %(status_code)s'
-        )
-        merged_config["formatters"]["default"]["fmt"] = DEFAULT_LOG_FORMAT
-        merged_config["formatters"]["default"]["datefmt"] = "%Y-%m-%d %H:%M"
+        logging_conf["formatters"] = {
+            "access": {
+                "fmt": "%(asctime)s.%(msecs)03d %(levelprefix)s "
+                       '%(client_addr)s - "%(request_line)s" %(status_code)s',
+            },
+            "default": {
+                "fmt": DEFAULT_LOG_FORMAT,
+                "datefmt": "%Y-%m-%d %H:%M",
+            },
+        }
+
+    merged_config = deep_update(deepcopy(uvicorn.config.LOGGING_CONFIG), logging_conf)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` around lines 119 - 128, The post-merge mutations to merged_config
(setting merged_config["loggers"]["uvicorn"]["handlers"],
merged_config["loggers"]["uvicorn.access"]["handlers"], and the formatters under
merged_config) should be moved into the logging_conf construction before calling
deep_update so the deep_update call produces the final merged_config without
later edits; update the logic that currently branches on handler == "rich" to
instead set the corresponding keys on logging_conf (e.g.,
logging_conf["loggers"]["uvicorn"]["handlers"],
logging_conf["loggers"]["uvicorn.access"]["handlers"] or
logging_conf["formatters"]["access"]["fmt"],
logging_conf["formatters"]["default"]["fmt"] and
logging_conf["formatters"]["default"]["datefmt"] using DEFAULT_LOG_FORMAT) and
then call deep_update(base, logging_conf) once to produce merged_config.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runners/uvicorn.py`:
- Around line 17-25: Fix the malformed docstring for the Uvicorn server starter
in src/runners/uvicorn.py: rewrite the Parameters section so each parameter has
its own complete description (e.g., configuration: ServiceConfiguration
providing host, port, workers and tls_config; log_config: dict for logging
configuration), ensure tls_config and its fields (`tls_key_path`,
`tls_certificate_path`, `tls_key_password`) are documented as possibly None, and
add a Returns section documenting that the function returns None; update the
summary and parameter order to follow Google Python docstring conventions.

---

Duplicate comments:
In `@src/log.py`:
- Line 13: Replace the internal import of pydantic.v1.utils.deep_update with a
local helper: remove the line importing deep_update and add a small
self-contained deep_update function in src/log.py that recursively merges
dict-like structures (preserving nested dicts and replacing non-dict values),
then update all usages in this file to call the new local deep_update helper
(referenced by its function name deep_update) so the module no longer depends on
pydantic internals.
- Around line 82-83: Update the setup_logging() docstring to describe side
effects and caching: state that setup_logging() globally configures Python
logging by calling dictConfig (applies side effects), is decorated with
lru_cache so repeated calls return the cached configuration, and return value is
the merged dict configuration (handlers, formatters, loggers, level info) that
was applied; mention any notable keys in the returned structure and that callers
receive the applied config rather than a fresh copy.
- Around line 119-128: The post-merge mutations to merged_config (setting
merged_config["loggers"]["uvicorn"]["handlers"],
merged_config["loggers"]["uvicorn.access"]["handlers"], and the formatters under
merged_config) should be moved into the logging_conf construction before calling
deep_update so the deep_update call produces the final merged_config without
later edits; update the logic that currently branches on handler == "rich" to
instead set the corresponding keys on logging_conf (e.g.,
logging_conf["loggers"]["uvicorn"]["handlers"],
logging_conf["loggers"]["uvicorn.access"]["handlers"] or
logging_conf["formatters"]["access"]["fmt"],
logging_conf["formatters"]["default"]["fmt"] and
logging_conf["formatters"]["default"]["datefmt"] using DEFAULT_LOG_FORMAT) and
then call deep_update(base, logging_conf) once to produce merged_config.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b6d1f3f5-970f-406c-a46d-ace4906571a5

📥 Commits

Reviewing files that changed from the base of the PR and between b321e1d and 1b0f4ac.

📒 Files selected for processing (3)
  • src/constants.py
  • src/log.py
  • src/runners/uvicorn.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: radon
  • GitHub Check: mypy
  • GitHub Check: Pylinter
  • GitHub Check: spectral
  • GitHub Check: integration_tests (3.13)
  • GitHub Check: unit_tests (3.12)
  • GitHub Check: unit_tests (3.13)
  • GitHub Check: build-pr
  • GitHub Check: E2E: server mode / ci / group 3
  • GitHub Check: E2E: library mode / ci / group 1
  • GitHub Check: E2E: server mode / ci / group 2
  • GitHub Check: E2E: library mode / ci / group 2
  • GitHub Check: E2E: library mode / ci / group 3
  • GitHub Check: E2E: server mode / ci / group 1
  • GitHub Check: E2E Tests for Lightspeed Evaluation job
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use absolute imports for internal modules: from authentication import get_auth_dependency
Llama Stack imports: Use from llama_stack_client import AsyncLlamaStackClient
Check constants.py for shared constants before defining new ones
All modules must start with descriptive docstrings explaining purpose
Use logger = get_logger(__name__) from log.py for module logging
All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings
Use snake_case with descriptive, action-oriented names for functions (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying function parameters
Use async def for I/O operations and external API calls
Use standard log levels with clear purposes: debug() for diagnostic info, info() for program execution, warning() for unexpected events, error() for serious problems
All classes must have descriptive docstrings explaining purpose and use PascalCase with standard suffixes: Configuration, Error/Exception, Resolver, Interface
Abstract classes must use ABC with @abstractmethod decorators
Follow Google Python docstring conventions with required sections: Parameters, Returns, Raises, and Attributes for classes

Files:

  • src/runners/uvicorn.py
  • src/constants.py
  • src/log.py
src/constants.py

📄 CodeRabbit inference engine (AGENTS.md)

Use constants.py for shared constants with descriptive comments and type hints using Final[type]

Files:

  • src/constants.py
🔇 Additional comments (3)
src/log.py (2)

25-27: LGTM — millisecond formatting helper is well-implemented.

The helper correctly converts microseconds to 3-digit milliseconds and returns Rich's Text type for use in log_time_format.


98-98: ⚡ Quick win

The code correctly uses a callable for log_time_format.

The Rich library's RichHandler explicitly accepts Union[str, FormatTimeCallable] for the log_time_format parameter, so passing _ms_time_format (a callable) is fully supported and correct.

src/runners/uvicorn.py (1)

29-30: LGTM — correct fallback to unified logging configuration.

The conditional call to setup_logging() properly applies the default merged configuration when log_config is not provided.

Comment thread src/runners/uvicorn.py Outdated
@samdoran samdoran force-pushed the use-existing-logger branch from 1b0f4ac to e0e20f1 Compare May 11, 2026 22:09
Copy link
Copy Markdown
Contributor

@tisnik tisnik left a comment

Choose a reason for hiding this comment

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

please try to figure out why unit tests fail. LGTM in overall

@samdoran
Copy link
Copy Markdown
Contributor Author

Yup, I'm working on the unit test failures.

samdoran added 17 commits May 12, 2026 15:52
Meroge the existing config from uvicorn with the logging for lightspeed-stack
with some modifications and pass that to uvicorn. This ensures the logging
configs work together and do not clobber each other.

Call setup_logging() early in the main entrypoint.
…y in __name__.

The logging library assumes __name__ will be “package.module.module”. Since this
project does not have a package, the value for __name__ varies widely in each
module. Thise breaks design assumpmtions of logging.

To work around this, define a default logger name that is used as the primary
configuration and add a helper function to always get the logger with a name
that aligns with how logging works.
Since there is no root package for this project, manually set the logger
and get the filename in order to show where the log message was emitted.
Use levelprefix for uvicorn.logging.DefaultFormatte.
Move filename and position to end of line so that information is arranged
in most important order from left to right within the line, where the message
came from being least relevant in my thinking compared to the time, log level,
and actual message.
When rich is not selected, use the uvicorn.logging.DefaultFormatter for log
messages. Modify the default format slightly to include miliseconds in the
timestamp.
Add a description of the problem to be addressed in the future.
The default .%f handling in RichHandler gives microseconds and strftime
does not provide a milisecond format string.
@samdoran samdoran force-pushed the use-existing-logger branch from e0e20f1 to d07f50b Compare May 12, 2026 21:03
@samdoran
Copy link
Copy Markdown
Contributor Author

The problem with the tests was caplog relying on propagation of log messages. I had to change the fixture that configures logging for the tests to ensure propagation is enabled (it is not in the new default config) and then restore state after each test.

Copy link
Copy Markdown
Contributor

@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: 6

♻️ Duplicate comments (2)
src/log.py (2)

13-13: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Replace pydantic.v1.utils.deep_update with a self-contained helper.

pydantic.v1.utils is part of the v1 compatibility layer, not a stable public API. Relying on internal utilities creates an unnecessary coupling for a simple recursive merge.

♻️ Proposed replacement

Remove the pydantic import:

-from pydantic.v1.utils import deep_update

Add a local helper function after the imports:

def _deep_update(base: dict, override: dict) -> dict:
    """Recursively merge override into base, returning a new dict."""
    result = base.copy()
    for key, value in override.items():
        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
            result[key] = _deep_update(result[key], value)
        else:
            result[key] = value
    return result

Then replace the call on line 117:

-    merged_config = deep_update(deepcopy(uvicorn.config.LOGGING_CONFIG), logging_conf)
+    merged_config = _deep_update(deepcopy(uvicorn.config.LOGGING_CONFIG), logging_conf)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` at line 13, Replace the fragile import of deep_update from
pydantic.v1.utils with a small self-contained recursive merge helper: remove the
import "from pydantic.v1.utils import deep_update", add a local function named
_deep_update(base: dict, override: dict) that recursively merges override into
base (creating and returning a new dict), and then update any call sites that
reference deep_update (e.g., the call in the logging configuration merge where
deep_update(...) is used) to call _deep_update(...) instead so the code no
longer depends on pydantic's internal API.

81-83: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Expand docstring to follow Google-style conventions.

The coding guidelines require descriptive docstrings following Google Python docstring conventions with required sections including Returns. The current one-line docstring omits the Returns section and does not explain side effects or caching behavior.

📝 Proposed docstring
 `@lru_cache`
 def setup_logging() -> dict[t.Any, t.Any]:
-    """Create logging configuration."""
+    """Configure and return the merged logging configuration.
+
+    Selects a Rich or uvicorn default handler based on TTY detection and the
+    LIGHTSPEED_STACK_DISABLE_RICH_HANDLER environment variable, merges the
+    computed configuration into uvicorn's default logging config, applies it
+    via logging.config.dictConfig, and returns the merged configuration dict.
+
+    Result is cached; call setup_logging.cache_clear() to force re-initialization.
+
+    Returns:
+        dict: The merged logging configuration passed to dictConfig.
+    """

As per coding guidelines, all functions must include descriptive docstrings following Google Python docstring conventions with required sections: Parameters, Returns, Raises.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/log.py` around lines 81 - 83, The setup_logging function has only a
one-line docstring; update it to a Google-style docstring that documents that
the function configures the global logging system (side effects), is cached via
`@lru_cache` (so repeated calls return the same dict), lists Parameters: none (or
omit if preferred), a Returns section describing the returned logging
configuration dict (type: dict[Any, Any] or more specific mapping of
handlers/formatters), and a Raises section (e.g., none expected or list specific
exceptions if applicable); reference the setup_logging function and its
`@lru_cache` decorator when editing the docstring so it clearly states caching
behavior and side effects.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/app/endpoints/test_rlsapi_v1.py`:
- Around line 791-806: The logger name construction in the caplog.at_level call
uses a double-dot: f"{constants.DEFAULT_LOGGER_NAME}..app.endpoints.rlsapi_v1"
which produces a malformed name; update that expression to use a single-dot and
guard against a trailing dot on DEFAULT_LOGGER_NAME—for example replace with
f"{constants.DEFAULT_LOGGER_NAME.rstrip('.')}.app.endpoints.rlsapi_v1" (change
in the test that calls caplog.at_level, referencing
constants.DEFAULT_LOGGER_NAME and the logger string).
- Around line 709-726: The test constructs a malformed logger name with a
double-dot in caplog.at_level; change the logger argument from
f"{constants.DEFAULT_LOGGER_NAME}..app.endpoints.rlsapi_v1" to use a single dot
(e.g., f"{constants.DEFAULT_LOGGER_NAME}.app.endpoints.rlsapi_v1") so caplog
attaches to the correct logger; update the string in the test around the
caplog.at_level call (referencing constants.DEFAULT_LOGGER_NAME and the
caplog.at_level invocation) to remove the extra dot.

In `@tests/unit/authorization/test_azure_token_manager.py`:
- Around line 140-147: The logger name in the test uses a malformed double-dot:
change the f-string constructing the logger in the caplog.at_level call from
f"{DEFAULT_LOGGER_NAME}..authorization.azure_token_manager" to use a single dot
(e.g., f"{DEFAULT_LOGGER_NAME}.authorization.azure_token_manager") so the logger
hierarchy is correct; update the caplog context around
token_manager.refresh_token() accordingly to use DEFAULT_LOGGER_NAME with a
single dot.

In `@tests/unit/models/config/test_rlsapi_v1_configuration.py`:
- Around line 164-169: The caplog.at_level call builds a malformed logger name
using a double-dot string; change the logger argument to use a single-dot
separator by replacing f"{DEFAULT_LOGGER_NAME}..models.config" with
f"{DEFAULT_LOGGER_NAME}.models.config" so the test uses a correct hierarchical
logger name (affecting the caplog context around Configuration(**config_dict)
and the subsequent assertion).
- Around line 137-142: The logger name in the test uses a malformed double-dot;
update the logger string in the caplog.at_level call to use a single dot (change
f"{DEFAULT_LOGGER_NAME}..models.config" to
f"{DEFAULT_LOGGER_NAME}.models.config") so the logger hierarchy is correct;
locate the caplog.at_level invocation in the test (and the DEFAULT_LOGGER_NAME
reference) and adjust the string accordingly.

In `@tests/unit/runners/test_uvicorn_runner.py`:
- Line 23: Add a unit test that covers the default log_config=None path by
calling start_uvicorn(configuration) without passing log_config, patching
runners.uvicorn.setup_logging to return a controlled dict and asserting it was
called, and patching uvicorn.run to assert its log_config parameter equals the
mocked return value; reference start_uvicorn (the function under test),
setup_logging (to be mocked), and uvicorn.run (to assert call) when implementing
the test in tests/unit/runners/test_uvicorn_runner.py.

---

Duplicate comments:
In `@src/log.py`:
- Line 13: Replace the fragile import of deep_update from pydantic.v1.utils with
a small self-contained recursive merge helper: remove the import "from
pydantic.v1.utils import deep_update", add a local function named
_deep_update(base: dict, override: dict) that recursively merges override into
base (creating and returning a new dict), and then update any call sites that
reference deep_update (e.g., the call in the logging configuration merge where
deep_update(...) is used) to call _deep_update(...) instead so the code no
longer depends on pydantic's internal API.
- Around line 81-83: The setup_logging function has only a one-line docstring;
update it to a Google-style docstring that documents that the function
configures the global logging system (side effects), is cached via `@lru_cache`
(so repeated calls return the same dict), lists Parameters: none (or omit if
preferred), a Returns section describing the returned logging configuration dict
(type: dict[Any, Any] or more specific mapping of handlers/formatters), and a
Raises section (e.g., none expected or list specific exceptions if applicable);
reference the setup_logging function and its `@lru_cache` decorator when editing
the docstring so it clearly states caching behavior and side effects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 48a93a80-1deb-4ae1-bffc-dafd39deef27

📥 Commits

Reviewing files that changed from the base of the PR and between 1b0f4ac and d07f50b.

📒 Files selected for processing (10)
  • src/constants.py
  • src/lightspeed_stack.py
  • src/log.py
  • src/runners/uvicorn.py
  • tests/unit/app/endpoints/test_rlsapi_v1.py
  • tests/unit/authorization/test_azure_token_manager.py
  • tests/unit/conftest.py
  • tests/unit/models/config/test_rlsapi_v1_configuration.py
  • tests/unit/runners/test_uvicorn_runner.py
  • tests/unit/test_log.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: shellcheck
  • GitHub Check: black
  • GitHub Check: Pyright
  • GitHub Check: Pylinter
  • GitHub Check: unit_tests (3.13)
  • GitHub Check: unit_tests (3.12)
  • GitHub Check: E2E: server mode / ci / group 2
  • GitHub Check: E2E: library mode / ci / group 3
  • GitHub Check: E2E: server mode / ci / group 1
  • GitHub Check: E2E: server mode / ci / group 3
  • GitHub Check: E2E: library mode / ci / group 2
  • GitHub Check: E2E Tests for Lightspeed Evaluation job
  • GitHub Check: E2E: library mode / ci / group 1
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use absolute imports for internal modules: from authentication import get_auth_dependency
Llama Stack imports: Use from llama_stack_client import AsyncLlamaStackClient
Check constants.py for shared constants before defining new ones
All modules must start with descriptive docstrings explaining purpose
Use logger = get_logger(__name__) from log.py for module logging
All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings
Use snake_case with descriptive, action-oriented names for functions (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying function parameters
Use async def for I/O operations and external API calls
Use standard log levels with clear purposes: debug() for diagnostic info, info() for program execution, warning() for unexpected events, error() for serious problems
All classes must have descriptive docstrings explaining purpose and use PascalCase with standard suffixes: Configuration, Error/Exception, Resolver, Interface
Abstract classes must use ABC with @abstractmethod decorators
Follow Google Python docstring conventions with required sections: Parameters, Returns, Raises, and Attributes for classes

Files:

  • src/lightspeed_stack.py
  • src/runners/uvicorn.py
  • src/constants.py
  • src/log.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Use pytest for all unit and integration tests; do not use unittest
Use pytest.mark.asyncio marker for async tests

Files:

  • tests/unit/conftest.py
  • tests/unit/authorization/test_azure_token_manager.py
  • tests/unit/runners/test_uvicorn_runner.py
  • tests/unit/models/config/test_rlsapi_v1_configuration.py
  • tests/unit/app/endpoints/test_rlsapi_v1.py
  • tests/unit/test_log.py
tests/**/conftest.py

📄 CodeRabbit inference engine (AGENTS.md)

Use conftest.py for shared pytest fixtures and pytest-mock for AsyncMock objects

Files:

  • tests/unit/conftest.py
src/constants.py

📄 CodeRabbit inference engine (AGENTS.md)

Use constants.py for shared constants with descriptive comments and type hints using Final[type]

Files:

  • src/constants.py
🔇 Additional comments (9)
src/constants.py (1)

228-233: LGTM!

tests/unit/conftest.py (1)

25-50: LGTM!

src/lightspeed_stack.py (1)

13-19: LGTM!

tests/unit/test_log.py (1)

14-74: LGTM!

Also applies to: 77-115

src/runners/uvicorn.py (5)

17-26: Fix the malformed docstring.

The docstring violates Google Python style conventions. This issue was already identified in a previous review.


7-7: LGTM!


13-16: LGTM!


39-39: LGTM!


29-30: The return type of setup_logging() is correctly annotated as dict[t.Any, t.Any] and is fully compatible with uvicorn.run(log_config=...).

The function merges uvicorn's built-in LOGGING_CONFIG with custom logging settings, ensuring the returned dictionary conforms to Uvicorn's expected logging configuration schema. No changes needed.

Comment thread tests/unit/app/endpoints/test_rlsapi_v1.py
Comment thread tests/unit/app/endpoints/test_rlsapi_v1.py
Comment thread tests/unit/authorization/test_azure_token_manager.py
Comment thread tests/unit/models/config/test_rlsapi_v1_configuration.py
Comment thread tests/unit/models/config/test_rlsapi_v1_configuration.py
Comment thread tests/unit/runners/test_uvicorn_runner.py
@samdoran samdoran force-pushed the use-existing-logger branch from c1f7bad to fe7fb9b Compare May 12, 2026 21:09
@samdoran samdoran force-pushed the use-existing-logger branch from ae1a1c9 to 4e8d95a Compare May 12, 2026 21:19
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