-
Notifications
You must be signed in to change notification settings - Fork 1.8k
ENG-10963 feat(log): add stdlib logging pipeline in reflex_base.utils.log (1/5) #6863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Framework logging now flows through standard python `logging` with per-module loggers (`reflex_base.utils.log`, re-exported as `reflex.utils.log`), bootstrapped on `import reflex`. Rich colored output is preserved at the sink, and `REFLEX_LOG_JSON` emits machine-readable JSON-lines records. `--loglevel critical` no longer prints the system-info banner (broken `LogLevel` string-compare ordering fixed). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Added `reflex_base.utils.log`: a standard python logging pipeline with a rich-rendering console handler (legacy colors preserved), a JSON-lines handler behind `REFLEX_LOG_JSON`, record deduplication, and file logging. `LogLevel` gained a correct total ordering and `to_logging_level()`, and the interactive console helpers (`print`/`rule`/`status`/`progress`) now respect JSON mode. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| import dataclasses | ||
| import enum | ||
| import importlib | ||
| import logging | ||
| import os | ||
| from collections.abc import Sequence | ||
| from functools import lru_cache | ||
|
|
@@ -27,6 +28,8 @@ | |
| from reflex_base.utils.exceptions import EnvironmentVarValueError | ||
| from reflex_base.utils.types import GenericType, is_union, value_inside_optional | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def get_default_value_for_field(field: dataclasses.Field) -> Any: | ||
| """Get the default value for a field. | ||
|
|
@@ -705,6 +708,9 @@ class EnvironmentVariables: | |
| # Enable full logging of debug messages to reflex user directory. | ||
| REFLEX_ENABLE_FULL_LOGGING: EnvVar[bool] = env_var(False) | ||
|
|
||
| # Emit logs as machine-readable JSON records instead of rich console output. | ||
| REFLEX_LOG_JSON: EnvVar[bool] = env_var(False) | ||
|
|
||
| # Whether to enable hot module replacement | ||
| VITE_HMR: EnvVar[bool] = env_var(True) | ||
|
|
||
|
|
@@ -781,13 +787,11 @@ def _load_dotenv_from_files(files: list[Path]): | |
| Args: | ||
| files: A list of Path objects representing the environment variable files. | ||
| """ | ||
| from reflex_base.utils import console | ||
|
|
||
| if not files: | ||
| return | ||
|
|
||
| if load_dotenv is None: | ||
| console.error( | ||
| logger.error( | ||
| """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.1.0"`.""" | ||
| ) | ||
|
Comment on lines
+794
to
796
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If Knowledge Base Used: Config and Environment System |
||
| return | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,19 +6,21 @@ | |||||||||
| import datetime | ||||||||||
| import functools | ||||||||||
| import inspect | ||||||||||
| import os | ||||||||||
| import shutil | ||||||||||
| import sys | ||||||||||
| import time | ||||||||||
| from collections.abc import Sequence | ||||||||||
| from pathlib import Path | ||||||||||
| from types import FrameType, ModuleType | ||||||||||
|
|
||||||||||
| from rich.console import Console | ||||||||||
| from rich.progress import MofNCompleteColumn, Progress, TaskID, TimeElapsedColumn | ||||||||||
| from rich.prompt import Prompt | ||||||||||
| from rich.table import Table | ||||||||||
|
|
||||||||||
| from reflex_base.constants import LogLevel | ||||||||||
| from reflex_base.constants.base import Reflex | ||||||||||
| from reflex_base.utils import log as _log | ||||||||||
| from reflex_base.utils.decorator import once | ||||||||||
|
|
||||||||||
| # Console for pretty printing. | ||||||||||
|
|
@@ -58,19 +60,11 @@ def set_log_level(log_level: LogLevel | None): | |||||||||
|
|
||||||||||
| Args: | ||||||||||
| log_level: The log level to set. | ||||||||||
|
|
||||||||||
| Raises: | ||||||||||
| TypeError: If the log level is a string. | ||||||||||
| """ | ||||||||||
| if log_level is None: | ||||||||||
| return | ||||||||||
| if not isinstance(log_level, LogLevel): | ||||||||||
| msg = f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead." | ||||||||||
| raise TypeError(msg) | ||||||||||
| _log.set_log_level(log_level) | ||||||||||
| global _LOG_LEVEL | ||||||||||
| if log_level != _LOG_LEVEL: | ||||||||||
| # Set the loglevel persistenly for subprocesses. | ||||||||||
| os.environ["REFLEX_LOGLEVEL"] = log_level.value | ||||||||||
| _LOG_LEVEL = log_level | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
@@ -91,6 +85,9 @@ def print(msg: str, *, dedupe: bool = False, **kwargs): | |||||||||
| dedupe: If True, suppress multiple console logs of print message. | ||||||||||
| kwargs: Keyword arguments to pass to the print function. | ||||||||||
| """ | ||||||||||
| if _log.is_json_mode(): | ||||||||||
| _log.emit_json_print(msg, dedupe=dedupe) | ||||||||||
| return | ||||||||||
|
Comment on lines
+88
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||||||||||
| if dedupe: | ||||||||||
| if msg in _EMITTED_PRINTS: | ||||||||||
| return | ||||||||||
|
|
@@ -106,6 +103,9 @@ def _print_stderr(msg: str, *, dedupe: bool = False, **kwargs): | |||||||||
| dedupe: If True, suppress multiple console logs of print message. | ||||||||||
| kwargs: Keyword arguments to pass to the print function. | ||||||||||
| """ | ||||||||||
| if _log.is_json_mode(): | ||||||||||
| _log.emit_json_print(msg, dedupe=dedupe, stderr=True) | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: In JSON mode, Prompt for AI agents |
||||||||||
| return | ||||||||||
| if dedupe: | ||||||||||
| if msg in _EMITTED_PRINTS: | ||||||||||
| return | ||||||||||
|
|
@@ -241,6 +241,8 @@ def rule(title: str, **kwargs): | |||||||||
| title: The title of the rule. | ||||||||||
| kwargs: Keyword arguments to pass to the print function. | ||||||||||
| """ | ||||||||||
| if _log.is_json_mode(): | ||||||||||
| return | ||||||||||
| _console.rule(title, **kwargs) | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
@@ -417,6 +419,27 @@ def ask( | |||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def print_table( | ||||||||||
| tabular_data: list[list[str]], | ||||||||||
| headers: Sequence[str] = (), | ||||||||||
| ) -> None: | ||||||||||
| """Print a table to the console. | ||||||||||
|
|
||||||||||
| Args: | ||||||||||
| tabular_data: The data to print in tabular format. | ||||||||||
| headers: The headers for the table. | ||||||||||
| """ | ||||||||||
| table = Table() | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents
Suggested change
|
||||||||||
|
|
||||||||||
| for column in headers: | ||||||||||
| table.add_column(column) | ||||||||||
|
|
||||||||||
| for row in tabular_data: | ||||||||||
| table.add_row(*row) | ||||||||||
|
|
||||||||||
| _console.print(table) | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||||||||||
|
|
||||||||||
|
|
||||||||||
| def progress(): | ||||||||||
| """Create a new progress bar. | ||||||||||
|
|
||||||||||
|
|
@@ -427,6 +450,7 @@ def progress(): | |||||||||
| *Progress.get_default_columns()[:-1], | ||||||||||
| MofNCompleteColumn(), | ||||||||||
| TimeElapsedColumn(), | ||||||||||
| disable=_log.is_json_mode(), | ||||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
@@ -440,6 +464,8 @@ def status(*args, **kwargs): | |||||||||
| Returns: | ||||||||||
| A new status. | ||||||||||
| """ | ||||||||||
| if _log.is_json_mode(): | ||||||||||
| return _log._quiet_console.status(*args, **kwargs) | ||||||||||
| return _console.status(*args, **kwargs) | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: This error now goes through a plain
logging.getLogger(__name__)call instead ofconsole.error(). Ifreflex_base.environmentis imported beforereflex(solog.bootstrap()hasn't claimed this logger yet), the record falls back to default stdlib handling — plain stderr text instead of a JSON record, and it bypasses full-logging file capture — breaking the logging contract this PR establishes.Prompt for AI agents