mirror of
https://github.com/exo-explore/exo.git
synced 2026-08-01 08:07:55 -04:00
-q and -v migrated
This commit is contained in:
@@ -15,8 +15,8 @@ cd dashboard && npm install && npm run build && cd ..
|
||||
# Run exo (starts both master and worker with API at http://localhost:52415)
|
||||
uv run exo
|
||||
|
||||
# Run with verbose logging
|
||||
uv run exo -v # or -vv for more verbose
|
||||
# Run with debug logging
|
||||
uv run exo --verbosity=debug # or: uv run exo -v debug
|
||||
|
||||
# Run tests (excludes slow tests by default)
|
||||
uv run pytest
|
||||
|
||||
12
README.md
12
README.md
@@ -313,6 +313,7 @@ exo supports several environment variables for configuration:
|
||||
| `EXO_OFFLINE` | Startup default for `--offline` when the CLI flag is omitted. Uses only local models and skips internet checks. | `false` |
|
||||
| `EXO_ENABLE_IMAGE_MODELS` | Enable image model support | `false` |
|
||||
| `EXO_ZENOH_NAMESPACE` | Custom namespace for cluster isolation | None |
|
||||
| `EXO_VERBOSITY` | Startup default for `--verbosity=<LEVEL>` when the CLI flag is omitted. Valid levels: `off`, `error`, `warn`, `info`, `debug`, `trace`. | `info` |
|
||||
| `EXO_FAST_SYNCH` | Startup default for `--fast-synch=true\|false` when the CLI flag is omitted (for JACCL backend). Leave unset for automatic behavior. | Auto |
|
||||
| `EXO_TRACING_ENABLED` | Enable distributed tracing for performance analysis | `false` |
|
||||
|
||||
@@ -335,6 +336,17 @@ EXO_ENABLE_IMAGE_MODELS=true uv run exo
|
||||
# Use custom namespace for cluster isolation
|
||||
EXO_ZENOH_NAMESPACE=my-dev-cluster uv run exo
|
||||
|
||||
# Set logging verbosity (CLI or ENV arg)
|
||||
uv run exo --verbosity=debug
|
||||
uv run exo -v debug
|
||||
EXO_VERBOSITY=debug uv run exo
|
||||
|
||||
# Only show error logs
|
||||
uv run exo --quiet
|
||||
|
||||
# Disable logging output
|
||||
uv run exo --verbosity=off
|
||||
|
||||
# Force MLX FAST_SYNCH off (CLI or ENV arg)
|
||||
uv run exo --fast-synch=false
|
||||
EXO_FAST_SYNCH=false uv run exo
|
||||
|
||||
@@ -430,3 +430,8 @@ class VerbosityFilter(enum.Enum):
|
||||
Debug = ...
|
||||
Trace = ...
|
||||
|
||||
def __lt__(self, other: object, /) -> bool: ...
|
||||
def __le__(self, other: object, /) -> bool: ...
|
||||
def __gt__(self, other: object, /) -> bool: ...
|
||||
def __ge__(self, other: object, /) -> bool: ...
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ pub struct AppArgs {
|
||||
long = "quiet",
|
||||
action = ArgAction::SetTrue,
|
||||
conflicts_with = "verbosity",
|
||||
help = "Disable non-error logging (alias for --verbosity=off)"
|
||||
help = "Only show error logs (alias for --verbosity=error)"
|
||||
)]
|
||||
#[serde(skip)]
|
||||
verbosity_off: bool,
|
||||
@@ -49,7 +49,7 @@ pub struct AppArgs {
|
||||
env = "EXO_VERBOSITY",
|
||||
value_enum,
|
||||
default_value = "info", // TODO: when config.toml introduced, remove this
|
||||
default_value_if("verbosity_off", "true", Some("off")),
|
||||
default_value_if("verbosity_off", "true", Some("error")),
|
||||
value_name = "LEVEL",
|
||||
conflicts_with = "verbosity_off",
|
||||
help = "Set the verbosity filter"
|
||||
|
||||
@@ -14,11 +14,12 @@ pub mod path;
|
||||
|
||||
/// Verbosity level used by EXO's logger.
|
||||
#[gen_stub_pyclass_enum]
|
||||
#[pyclass(eq, eq_int, from_py_object)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
|
||||
#[pyclass(eq, eq_int, ord, from_py_object)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ValueEnum)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[repr(u8)]
|
||||
pub enum VerbosityFilter {
|
||||
Off,
|
||||
Off = 0,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
@@ -26,6 +27,18 @@ pub enum VerbosityFilter {
|
||||
Trace,
|
||||
}
|
||||
|
||||
pyo3_stub_gen::inventory::submit! {
|
||||
pyo3_stub_gen::derive::gen_methods_from_python! {
|
||||
r#"
|
||||
class VerbosityFilter:
|
||||
def __lt__(self, other: object, /) -> bool: ...
|
||||
def __le__(self, other: object, /) -> bool: ...
|
||||
def __gt__(self, other: object, /) -> bool: ...
|
||||
def __ge__(self, other: object, /) -> bool: ...
|
||||
"#
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_submodule(m: &Bound<PyModule>) -> PyResult<()> {
|
||||
bootstrap_submodule(m)?;
|
||||
app_submodule(m)?;
|
||||
|
||||
@@ -10,7 +10,13 @@ from typing import Self
|
||||
import anyio
|
||||
from anyio.lowlevel import checkpoint as anyio_checkpoint
|
||||
from daemon import DaemonContext # pyright: ignore[reportMissingTypeStubs]
|
||||
from exo_rs import AppSettings, BootstrapSettings, CliArgs, Pidfile, PidfileError
|
||||
from exo_rs import (
|
||||
AppSettings,
|
||||
BootstrapSettings,
|
||||
CliArgs,
|
||||
Pidfile,
|
||||
PidfileError,
|
||||
)
|
||||
from loguru import logger
|
||||
from pydantic import PositiveInt
|
||||
|
||||
@@ -343,7 +349,7 @@ def main_inner(args: "Args", cli_args: CliArgs):
|
||||
mp.set_start_method("spawn", force=True)
|
||||
|
||||
# TODO: Refactor the current verbosity system
|
||||
logger_setup(config.bootstrap().log_files.exo_log, args.verbosity)
|
||||
logger_setup(config.bootstrap().log_files.exo_log, config.app().verbosity)
|
||||
|
||||
logger.info(f"pid = {os.getpid()}")
|
||||
if os.getenv("EXO_LIBP2P_NAMESPACE"):
|
||||
@@ -377,7 +383,6 @@ def main_inner(args: "Args", cli_args: CliArgs):
|
||||
|
||||
|
||||
class Args(FrozenModel):
|
||||
verbosity: int = 0
|
||||
api_port: PositiveInt = 52415
|
||||
bootstrap_peers: list[str] = []
|
||||
namespace: str
|
||||
@@ -387,22 +392,6 @@ class Args(FrozenModel):
|
||||
@classmethod
|
||||
def parse(cls) -> Self:
|
||||
parser = argparse.ArgumentParser(prog="EXO")
|
||||
default_verbosity = 0
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_const",
|
||||
const=-1,
|
||||
dest="verbosity",
|
||||
default=default_verbosity,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="count",
|
||||
dest="verbosity",
|
||||
default=default_verbosity,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--api-port",
|
||||
type=int,
|
||||
@@ -498,6 +487,22 @@ class Args(FrozenModel):
|
||||
default=argparse.SUPPRESS,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
dest="_verbosity_off_ignored",
|
||||
default=argparse.SUPPRESS,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbosity",
|
||||
nargs="?",
|
||||
dest="_verbosity_ignored",
|
||||
default=argparse.SUPPRESS,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
parsed_args = vars(args)
|
||||
@@ -511,5 +516,7 @@ class Args(FrozenModel):
|
||||
parsed_args.pop("_offline_ignored", None)
|
||||
parsed_args.pop("_no_batch_ignored", None)
|
||||
parsed_args.pop("_fast_synch_ignored", None)
|
||||
parsed_args.pop("_verbosity_off_ignored", None)
|
||||
parsed_args.pop("_verbosity_ignored", None)
|
||||
|
||||
return cls(**parsed_args) # pyright: ignore[reportAny] - We are intentionally validating here, we can't do it statically
|
||||
|
||||
@@ -4,6 +4,7 @@ from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
import zstandard
|
||||
from exo_rs import VerbosityFilter
|
||||
from hypercorn import Config
|
||||
from hypercorn.logging import Logger as HypercornLogger
|
||||
from loguru import logger
|
||||
@@ -43,7 +44,27 @@ class _InterceptHandler(logging.Handler):
|
||||
logger.opt(depth=3, exception=record.exc_info).log(level, record.getMessage())
|
||||
|
||||
|
||||
def logger_setup(log_file: Path | None, verbosity: int = 0):
|
||||
def _loguru_log_level(verbosity: VerbosityFilter):
|
||||
match verbosity:
|
||||
case VerbosityFilter.Off:
|
||||
raise ValueError(
|
||||
"VerbosityFilter.Off does not translate to a loguru log-level"
|
||||
)
|
||||
case VerbosityFilter.Error:
|
||||
return "ERROR"
|
||||
case VerbosityFilter.Warn:
|
||||
return "WARNING"
|
||||
case VerbosityFilter.Info:
|
||||
return "INFO"
|
||||
case VerbosityFilter.Debug:
|
||||
return "DEBUG"
|
||||
case VerbosityFilter.Trace:
|
||||
return "TRACE"
|
||||
|
||||
|
||||
def logger_setup(
|
||||
log_file: Path | None, verbosity: VerbosityFilter = VerbosityFilter.Info
|
||||
):
|
||||
"""Set up logging for this process - formatting, file handles, verbosity and output"""
|
||||
|
||||
logging.getLogger("exo_rs").setLevel(logging.INFO)
|
||||
@@ -56,11 +77,17 @@ def logger_setup(log_file: Path | None, verbosity: int = 0):
|
||||
# replace all stdlib loggers with _InterceptHandlers that log to loguru
|
||||
logging.basicConfig(handlers=[_InterceptHandler()], level=0)
|
||||
|
||||
if verbosity == 0:
|
||||
# if Off then no logging - return early
|
||||
if verbosity == VerbosityFilter.Off:
|
||||
return
|
||||
|
||||
# info (or less verbose than info) gets a different formatter
|
||||
level = _loguru_log_level(verbosity)
|
||||
if verbosity <= VerbosityFilter.Info:
|
||||
logger.add(
|
||||
sys.__stderr__, # type: ignore
|
||||
format="[ {time:hh:mm:ss.SSSSA} | <level>{level: <8}</level>] <level>{message}</level>",
|
||||
level="INFO",
|
||||
level=level,
|
||||
colorize=True,
|
||||
enqueue=True,
|
||||
)
|
||||
@@ -68,7 +95,7 @@ def logger_setup(log_file: Path | None, verbosity: int = 0):
|
||||
logger.add(
|
||||
sys.__stderr__, # type: ignore
|
||||
format="[ {time:YYYY-MM-DD HH:mm:ss.SSS} | <level>{level: <8}</level> | {name}:{function}:{line} ] <level>{message}</level>",
|
||||
level="DEBUG",
|
||||
level=level,
|
||||
colorize=True,
|
||||
enqueue=True,
|
||||
)
|
||||
@@ -77,7 +104,7 @@ def logger_setup(log_file: Path | None, verbosity: int = 0):
|
||||
logger.add(
|
||||
log_file,
|
||||
format="[ {time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} ] {message}",
|
||||
level="DEBUG" if verbosity > 0 else "INFO",
|
||||
level=level,
|
||||
colorize=False,
|
||||
enqueue=True,
|
||||
rotation=lambda _, __: next(rotate_once),
|
||||
@@ -89,29 +116,3 @@ def logger_setup(log_file: Path | None, verbosity: int = 0):
|
||||
def logger_cleanup():
|
||||
"""Flush all queues before shutting down so any in-flight logs are written to disk"""
|
||||
logger.complete()
|
||||
|
||||
|
||||
""" --- TODO: Capture MLX Log output:
|
||||
import contextlib
|
||||
import sys
|
||||
from loguru import logger
|
||||
|
||||
class StreamToLogger:
|
||||
|
||||
def __init__(self, level="INFO"):
|
||||
self._level = level
|
||||
|
||||
def write(self, buffer):
|
||||
for line in buffer.rstrip().splitlines():
|
||||
logger.opt(depth=1).log(self._level, line.rstrip())
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
logger.remove()
|
||||
logger.add(sys.__stdout__)
|
||||
|
||||
stream = StreamToLogger()
|
||||
with contextlib.redirect_stdout(stream):
|
||||
print("Standard output is sent to added handlers.")
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user