Skip to content

ENG-10963 refactor(log): migrate the reflex CLI and runtime to logging (3/5) - #6865

Open
FarhanAliRaza wants to merge 1 commit into
farhan/eng-10963-log-2-migrate-basefrom
farhan/eng-10963-log-3-migrate-core
Open

ENG-10963 refactor(log): migrate the reflex CLI and runtime to logging (3/5)#6865
FarhanAliRaza wants to merge 1 commit into
farhan/eng-10963-log-2-migrate-basefrom
farhan/eng-10963-log-3-migrate-core

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Replaces the console helper call sites with per-module loggers across reflex/, and adds shared click log options in reflex.utils.cli_options including a new --json flag that switches output to JSON-lines records (REFLEX_LOG_JSON equivalent).

Keeps a temporary LogLevel conversion mapper for the hosting CLI boundary; it is removed in the next PR when the enums unify.

Stack (ENG-10963)

#6863#6864 → this → hosting CLI → deprecate.
Merge in order; each PR is based on the previous branch.

Review in cubic

Replace console.debug/info/warn/error call sites with per-module
loggers across reflex/, add shared click log options in
reflex.utils.cli_options including a new --json flag that switches
output to JSON-lines records (REFLEX_LOG_JSON equivalent).
@FarhanAliRaza
FarhanAliRaza requested a review from a team as a code owner August 10, 2026 20:36
@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

ENG-10963

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR migrates Reflex CLI and runtime diagnostics from console helpers to per-module Python loggers and introduces shared --loglevel/--json Click options.

  • Replaces console logging across app, state, compiler, database, process, and utility paths.
  • Adds JSON-lines logging mode to the main and custom-component CLI commands.
  • Centralizes progress and timing integration around the new logging pipeline.

Confidence Score: 4/5

The PR should not merge until JSON-mode compilation stops emitting plain-text progress records when --no-rich is selected.

The new JSON output contract is broken by a directly reachable compile option combination because PoorProgress bypasses the JSON logging sink.

Files Needing Attention: reflex/compiler/compiler.py, reflex/utils/cli_options.py

Important Files Changed

Filename Overview
reflex/utils/cli_options.py Adds shared eager Click callbacks for log-level selection and JSON logging mode; callback composition preserves the prior command option surfaces.
reflex/reflex.py Migrates primary CLI diagnostics and success messages to module logging and applies shared logging options to public commands.
reflex/compiler/compiler.py Migrates compiler timing and diagnostics, but JSON mode combined with --no-rich still selects a progress implementation that emits plain text.
reflex/custom_components/custom_components.py Migrates component CLI diagnostics and adopts shared log options while retaining direct console helpers for interactive presentation.
reflex/app.py Replaces runtime application and websocket diagnostics with a module logger without changing control flow.
reflex/istate/manager/redis.py Migrates Redis state-manager diagnostics while preserving deduplication metadata and lock-handling behavior.
reflex/model.py Migrates database and migration diagnostics to standard logging without altering database operations.

Reviews (1): Last reviewed commit: "refactor(log): migrate the reflex CLI an..." | Re-trigger Greptile

if use_rich
else console.PoorProgress()
)
progress = console.progress() if use_rich else console.PoorProgress()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Plain-text progress breaks JSON output

When reflex compile --json --no-rich performs a frontend compilation, this branch selects PoorProgress, whose advance method writes plain-text Progress: N/M lines directly to the console, causing JSON-lines consumers to fail parsing the command output.

Knowledge Base Used:

@codspeed-hq

codspeed-hq Bot commented Aug 10, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing farhan/eng-10963-log-3-migrate-core (29505c0) with farhan/eng-10963-log-2-migrate-base (666df42)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 29505c0626

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread reflex/reflex.py

@cli.command()
@loglevel_option
@log_options

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep hosting login output JSON-safe

When reflex login --json is used, this option enables the reflex_base JSON handler but then delegates to reflex_cli.v2.cli.login(), which prints messages such as “You already logged in” and “Successfully logged in” through reflex_cli.utils.console; that console always calls Rich's _console.print and never checks REFLEX_LOG_JSON. The resulting stdout mixes plain text with JSON records, so JSON-lines consumers cannot parse it. The same boundary affects other hosting-backed commands, so either bridge/migrate the hosting console before exposing --json on them or withhold this option there.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

6 issues found across 39 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="reflex/reflex.py">

<violation number="1" location="reflex/reflex.py:96">
P2: The `login` command now exposes `--json` via `@log_options`, but it delegates to `reflex_cli.v2.cli.login()`, which prints through `reflex_cli.utils.console` (Rich's `_console.print`) and never checks the JSON log mode. Running `reflex login --json` will mix plain-text hosting CLI output with JSON-lines records, breaking JSON-lines consumers. Consider bridging/migrating the hosting console before exposing `--json` on hosting-backed commands, or withhold the option there until the hosting CLI is migrated.</violation>

<violation number="2" location="reflex/reflex.py:717">
P2: `reflex db` commands cannot accept the new `--json` flag, so their migrated log output is only JSON-capable through the environment variable. Apply `log_options` to the database subcommands (or provide equivalent group-level options) so CLI JSON output covers this command family.</violation>
</file>

<file name="tests/units/test_app.py">

<violation number="1" location="tests/units/test_app.py:352">
P3: These caplog assertions count records globally across every logger instead of scoping to the reflex.app logger/message, so any unrelated WARNING/ERROR emitted during the test (from add_page internals, config/deprecation paths, or pytest logging) turns the exact-count (`== 1`) assertion into a false failure. The previous targeted mocks (console.warn / console.error .assert_called_once) were immune to that noise; scope the filter to the app logger (e.g. r.name == "reflex.app") to keep the checks precise and stable.</violation>
</file>

<file name="tests/units/utils/test_utils.py">

<violation number="1" location="tests/units/utils/test_utils.py:790">
P2: This test no longer exercises the debug path it intends to. `output_system_info()` gates its body on `console.is_debug()` (`reflex_base.utils.console.is_debug`), which reads `console._LOG_LEVEL` — not `log._log_level`. The old test patched `reflex_base.utils.console._LOG_LEVEL` directly; the new one patches `reflex_base.utils.log._log_level`, which is a separate module global that only changes when `console.set_log_level()` is called. As a result `console.is_debug()` still returns `False` (default INFO), so `output_system_info()` returns early and the imports/iteration under the debug branch are no longer covered. The test still passes (its only assertion is that it doesn't crash), silently reducing coverage.</violation>
</file>

<file name="tests/units/test_state.py">

<violation number="1" location="tests/units/test_state.py:1927">
P3: The migrated `_clear_dedupe` clears `log._dedupe_filter().seen`, but `console.deprecate` still tracks its dedup in `_EMITTED_DEPRECATION_WARNINGS` (reflex_base/utils/console.py line 368) and never calls `dedupe_once`/`_dedupe_filter()`. So clearing the log filter's seen-set no longer resets the deprecation dedup the way the original `_base_console._EMITTED_DEPRECATION_WARNINGS -= ...` did; the helper is now a no-op against the storage `deprecate` actually uses. Point it back at `_EMITTED_DEPRECATION_WARNINGS` (or switch `deprecate` to the log dedupe and keep this).</violation>
</file>

<file name="reflex/compiler/compiler.py">

<violation number="1" location="reflex/compiler/compiler.py:1191">
P2: When `reflex compile` is run with `--json --no-rich`, `console.PoorProgress()` is selected as the fallback progress renderer, and its `advance` method writes plain-text `Progress: N/M` lines directly to stdout. This mixes non-JSON text into the output stream expected to be JSON-lines, breaking machine parsing for `--json` consumers.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread reflex/reflex.py
# Check the database url.
if config.db_url is None:
console.error("db_url is not configured, cannot initialize.")
logger.error("db_url is not configured, cannot initialize.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: reflex db commands cannot accept the new --json flag, so their migrated log output is only JSON-capable through the environment variable. Apply log_options to the database subcommands (or provide equivalent group-level options) so CLI JSON output covers this command family.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/reflex.py, line 717:

<comment>`reflex db` commands cannot accept the new `--json` flag, so their migrated log output is only JSON-capable through the environment variable. Apply `log_options` to the database subcommands (or provide equivalent group-level options) so CLI JSON output covers this command family.</comment>

<file context>
@@ -735,12 +714,12 @@ def db_init():
     # Check the database url.
     if config.db_url is None:
-        console.error("db_url is not configured, cannot initialize.")
+        logger.error("db_url is not configured, cannot initialize.")
         return
 
</file context>

without crashing.
"""
mocker.patch("reflex_base.utils.console._LOG_LEVEL", constants.LogLevel.DEBUG)
mocker.patch("reflex_base.utils.log._log_level", constants.LogLevel.DEBUG)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This test no longer exercises the debug path it intends to. output_system_info() gates its body on console.is_debug() (reflex_base.utils.console.is_debug), which reads console._LOG_LEVEL — not log._log_level. The old test patched reflex_base.utils.console._LOG_LEVEL directly; the new one patches reflex_base.utils.log._log_level, which is a separate module global that only changes when console.set_log_level() is called. As a result console.is_debug() still returns False (default INFO), so output_system_info() returns early and the imports/iteration under the debug branch are no longer covered. The test still passes (its only assertion is that it doesn't crash), silently reducing coverage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/units/utils/test_utils.py, line 790:

<comment>This test no longer exercises the debug path it intends to. `output_system_info()` gates its body on `console.is_debug()` (`reflex_base.utils.console.is_debug`), which reads `console._LOG_LEVEL` — not `log._log_level`. The old test patched `reflex_base.utils.console._LOG_LEVEL` directly; the new one patches `reflex_base.utils.log._log_level`, which is a separate module global that only changes when `console.set_log_level()` is called. As a result `console.is_debug()` still returns `False` (default INFO), so `output_system_info()` returns early and the imports/iteration under the debug branch are no longer covered. The test still passes (its only assertion is that it doesn't crash), silently reducing coverage.</comment>

<file context>
@@ -786,7 +787,7 @@ def test_output_system_info(mocker: MockerFixture):
     without crashing.
     """
-    mocker.patch("reflex_base.utils.console._LOG_LEVEL", constants.LogLevel.DEBUG)
+    mocker.patch("reflex_base.utils.log._log_level", constants.LogLevel.DEBUG)
     utils_exec.output_system_info()
 
</file context>

if use_rich
else console.PoorProgress()
)
progress = console.progress() if use_rich else console.PoorProgress()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When reflex compile is run with --json --no-rich, console.PoorProgress() is selected as the fallback progress renderer, and its advance method writes plain-text Progress: N/M lines directly to stdout. This mixes non-JSON text into the output stream expected to be JSON-lines, breaking machine parsing for --json consumers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/compiler/compiler.py, line 1191:

<comment>When `reflex compile` is run with `--json --no-rich`, `console.PoorProgress()` is selected as the fallback progress renderer, and its `advance` method writes plain-text `Progress: N/M` lines directly to stdout. This mixes non-JSON text into the output stream expected to be JSON-lines, breaking machine parsing for `--json` consumers.</comment>

<file context>
@@ -1176,24 +1179,16 @@ def compile_app(
-        if use_rich
-        else console.PoorProgress()
-    )
+    progress = console.progress() if use_rich else console.PoorProgress()
     fixed_steps = 7
     compiler_plugins, radix_themes_plugin = _resolve_radix_themes_plugin(
</file context>

Comment thread reflex/reflex.py

@cli.command()
@loglevel_option
@log_options

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The login command now exposes --json via @log_options, but it delegates to reflex_cli.v2.cli.login(), which prints through reflex_cli.utils.console (Rich's _console.print) and never checks the JSON log mode. Running reflex login --json will mix plain-text hosting CLI output with JSON-lines records, breaking JSON-lines consumers. Consider bridging/migrating the hosting console before exposing --json on hosting-backed commands, or withhold the option there until the hosting CLI is migrated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/reflex.py, line 96:

<comment>The `login` command now exposes `--json` via `@log_options`, but it delegates to `reflex_cli.v2.cli.login()`, which prints through `reflex_cli.utils.console` (Rich's `_console.print`) and never checks the JSON log mode. Running `reflex login --json` will mix plain-text hosting CLI output with JSON-lines records, breaking JSON-lines consumers. Consider bridging/migrating the hosting console before exposing `--json` on hosting-backed commands, or withhold the option there until the hosting CLI is migrated.</comment>

<file context>
@@ -110,11 +86,14 @@ def _init(
 
 @cli.command()
-@loglevel_option
+@log_options
 @click.option(
     "--name",
</file context>

Comment thread tests/units/test_app.py
)
app.add_page(second_page, route="/" + route.strip("/") if route else None)
assert mock_object.call_count == 1
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: These caplog assertions count records globally across every logger instead of scoping to the reflex.app logger/message, so any unrelated WARNING/ERROR emitted during the test (from add_page internals, config/deprecation paths, or pytest logging) turns the exact-count (== 1) assertion into a false failure. The previous targeted mocks (console.warn / console.error .assert_called_once) were immune to that noise; scope the filter to the app logger (e.g. r.name == "reflex.app") to keep the checks precise and stable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/units/test_app.py, line 352:

<comment>These caplog assertions count records globally across every logger instead of scoping to the reflex.app logger/message, so any unrelated WARNING/ERROR emitted during the test (from add_page internals, config/deprecation paths, or pytest logging) turns the exact-count (`== 1`) assertion into a false failure. The previous targeted mocks (console.warn / console.error .assert_called_once) were immune to that noise; scope the filter to the app logger (e.g. r.name == "reflex.app") to keep the checks precise and stable.</comment>

<file context>
@@ -344,17 +345,12 @@ def index():
-    )
     app.add_page(second_page, route="/" + route.strip("/") if route else None)
-    assert mock_object.call_count == 1
+    warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
+    assert len(warnings) == 1
 
</file context>

Comment thread tests/units/test_state.py

def _clear_dedupe():
_base_console._EMITTED_DEPRECATION_WARNINGS -= {
seen = log._dedupe_filter().seen

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The migrated _clear_dedupe clears log._dedupe_filter().seen, but console.deprecate still tracks its dedup in _EMITTED_DEPRECATION_WARNINGS (reflex_base/utils/console.py line 368) and never calls dedupe_once/_dedupe_filter(). So clearing the log filter's seen-set no longer resets the deprecation dedup the way the original _base_console._EMITTED_DEPRECATION_WARNINGS -= ... did; the helper is now a no-op against the storage deprecate actually uses. Point it back at _EMITTED_DEPRECATION_WARNINGS (or switch deprecate to the log dedupe and keep this).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/units/test_state.py, line 1927:

<comment>The migrated `_clear_dedupe` clears `log._dedupe_filter().seen`, but `console.deprecate` still tracks its dedup in `_EMITTED_DEPRECATION_WARNINGS` (reflex_base/utils/console.py line 368) and never calls `dedupe_once`/`_dedupe_filter()`. So clearing the log filter's seen-set no longer resets the deprecation dedup the way the original `_base_console._EMITTED_DEPRECATION_WARNINGS -= ...` did; the helper is now a no-op against the storage `deprecate` actually uses. Point it back at `_EMITTED_DEPRECATION_WARNINGS` (or switch `deprecate` to the log dedupe and keep this).</comment>

<file context>
@@ -1922,21 +1916,19 @@ async def test_state_manager_legacy_token(state_manager: StateManager, token: st
 
     def _clear_dedupe():
-        _base_console._EMITTED_DEPRECATION_WARNINGS -= {
+        seen = log._dedupe_filter().seen
+        seen -= {
             k
</file context>

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.

1 participant