feature: support uvicorn access log filter(disable logging /metrics) (#15513)
This commit is contained in:
@@ -182,6 +182,7 @@ Please consult the documentation below and [server_args.py](https://github.com/s
|
|||||||
| `--log-requests-level` | 0: Log metadata (no sampling parameters). 1: Log metadata and sampling parameters. 2: Log metadata, sampling parameters and partial input/output. 3: Log every input/output. | `2` | `0`, `1`, `2`, `3` |
|
| `--log-requests-level` | 0: Log metadata (no sampling parameters). 1: Log metadata and sampling parameters. 2: Log metadata, sampling parameters and partial input/output. 3: Log every input/output. | `2` | `0`, `1`, `2`, `3` |
|
||||||
| `--log-requests-format` | Format for request logging: 'text' (human-readable) or 'json' (structured) | `text` | `text`, `json` |
|
| `--log-requests-format` | Format for request logging: 'text' (human-readable) or 'json' (structured) | `text` | `text`, `json` |
|
||||||
| `--log-requests-target` | Target(s) for request logging: 'stdout' and/or directory path(s) for file output. Can specify multiple targets, e.g., '--log-requests-target stdout /my/path'. | `None` | List[str] |
|
| `--log-requests-target` | Target(s) for request logging: 'stdout' and/or directory path(s) for file output. Can specify multiple targets, e.g., '--log-requests-target stdout /my/path'. | `None` | List[str] |
|
||||||
|
| `--uvicorn-access-log-exclude-prefixes` | Exclude uvicorn access logs whose request path starts with any of these prefixes. Defaults to empty (disabled). | `[]` | List[str] |
|
||||||
| `--crash-dump-folder` | Folder path to dump requests from the last 5 min before a crash (if any). If not specified, crash dumping is disabled. | `None` | Type: str |
|
| `--crash-dump-folder` | Folder path to dump requests from the last 5 min before a crash (if any). If not specified, crash dumping is disabled. | `None` | Type: str |
|
||||||
| `--show-time-cost` | Show time cost of custom marks. | `False` | bool flag (set to enable) |
|
| `--show-time-cost` | Show time cost of custom marks. | `False` | bool flag (set to enable) |
|
||||||
| `--enable-metrics` | Enable log prometheus metrics. | `False` | bool flag (set to enable) |
|
| `--enable-metrics` | Enable log prometheus metrics. | `False` | bool flag (set to enable) |
|
||||||
|
|||||||
@@ -1817,7 +1817,7 @@ def launch_server(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Update logging configs
|
# Update logging configs
|
||||||
set_uvicorn_logging_configs()
|
set_uvicorn_logging_configs(server_args)
|
||||||
|
|
||||||
# Listen for HTTP requests
|
# Listen for HTTP requests
|
||||||
if server_args.tokenizer_worker_num == 1:
|
if server_args.tokenizer_worker_num == 1:
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ from sglang.utils import is_in_ci
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Define constants
|
# Define constants
|
||||||
|
DEFAULT_UVICORN_ACCESS_LOG_EXCLUDE_PREFIXES = ()
|
||||||
SAMPLING_BACKEND_CHOICES = {"flashinfer", "pytorch", "ascend"}
|
SAMPLING_BACKEND_CHOICES = {"flashinfer", "pytorch", "ascend"}
|
||||||
LOAD_FORMAT_CHOICES = [
|
LOAD_FORMAT_CHOICES = [
|
||||||
"auto",
|
"auto",
|
||||||
@@ -346,6 +347,9 @@ class ServerArgs:
|
|||||||
log_requests_level: int = 2
|
log_requests_level: int = 2
|
||||||
log_requests_format: str = "text"
|
log_requests_format: str = "text"
|
||||||
log_requests_target: Optional[List[str]] = None
|
log_requests_target: Optional[List[str]] = None
|
||||||
|
uvicorn_access_log_exclude_prefixes: List[str] = dataclasses.field(
|
||||||
|
default_factory=lambda: list(DEFAULT_UVICORN_ACCESS_LOG_EXCLUDE_PREFIXES)
|
||||||
|
)
|
||||||
crash_dump_folder: Optional[str] = None
|
crash_dump_folder: Optional[str] = None
|
||||||
show_time_cost: bool = False
|
show_time_cost: bool = False
|
||||||
enable_metrics: bool = False
|
enable_metrics: bool = False
|
||||||
@@ -3112,6 +3116,15 @@ class ServerArgs:
|
|||||||
help="Target(s) for request logging: 'stdout' and/or directory path(s) for file output. "
|
help="Target(s) for request logging: 'stdout' and/or directory path(s) for file output. "
|
||||||
"Can specify multiple targets, e.g., '--log-requests-target stdout /my/path'. ",
|
"Can specify multiple targets, e.g., '--log-requests-target stdout /my/path'. ",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--uvicorn-access-log-exclude-prefixes",
|
||||||
|
type=str,
|
||||||
|
nargs="*",
|
||||||
|
default=list(DEFAULT_UVICORN_ACCESS_LOG_EXCLUDE_PREFIXES),
|
||||||
|
help="Exclude uvicorn access logs whose request path starts with any of these prefixes. "
|
||||||
|
"Defaults to empty (disabled). "
|
||||||
|
"Example: --uvicorn-access-log-exclude-prefixes /metrics /health",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--crash-dump-folder",
|
"--crash-dump-folder",
|
||||||
type=str,
|
type=str,
|
||||||
|
|||||||
@@ -2314,7 +2314,64 @@ def kill_itself_when_parent_died():
|
|||||||
logger.warning("kill_itself_when_parent_died is only supported in linux.")
|
logger.warning("kill_itself_when_parent_died is only supported in linux.")
|
||||||
|
|
||||||
|
|
||||||
def set_uvicorn_logging_configs():
|
class UvicornAccessLogFilter(logging.Filter):
|
||||||
|
"""Filter uvicorn access logs by request path.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Uvicorn access records usually provide `request_line` like: "GET /metrics HTTP/1.1".
|
||||||
|
- We defensively fall back to parsing `record.getMessage()` if needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, excluded_path_prefixes=None):
|
||||||
|
super().__init__()
|
||||||
|
excluded_path_prefixes = excluded_path_prefixes or []
|
||||||
|
# Normalize once: drop empty prefixes, stringify, keep as tuple (fast iteration, immutable).
|
||||||
|
self.excluded_path_prefixes = tuple(str(p) for p in excluded_path_prefixes if p)
|
||||||
|
|
||||||
|
def filter(self, record: logging.LogRecord) -> bool:
|
||||||
|
path = None
|
||||||
|
|
||||||
|
request_line = getattr(record, "request_line", None)
|
||||||
|
if request_line:
|
||||||
|
parts = str(request_line).split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
path = parts[1]
|
||||||
|
|
||||||
|
if not path:
|
||||||
|
# Fallback for non-standard formatters/records
|
||||||
|
try:
|
||||||
|
msg = record.getMessage()
|
||||||
|
except Exception:
|
||||||
|
msg = None
|
||||||
|
if msg:
|
||||||
|
q1 = msg.find('"')
|
||||||
|
q2 = msg.find('"', q1 + 1) if q1 != -1 else -1
|
||||||
|
if q1 != -1 and q2 != -1:
|
||||||
|
rl = msg[q1 + 1 : q2]
|
||||||
|
parts = rl.split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
path = parts[1]
|
||||||
|
|
||||||
|
if not path:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Strip query string for matching
|
||||||
|
path = str(path)
|
||||||
|
# Some proxies/clients may emit absolute-form request-target in logs:
|
||||||
|
# e.g. "GET https://example.com/metrics HTTP/1.1" -> extract "/metrics".
|
||||||
|
if "://" in path:
|
||||||
|
try:
|
||||||
|
path = urlparse(path).path or path
|
||||||
|
except Exception:
|
||||||
|
# If parsing fails, fall back to the raw value.
|
||||||
|
pass
|
||||||
|
path = path.split("?", 1)[0]
|
||||||
|
return not any(
|
||||||
|
path.startswith(prefix) for prefix in self.excluded_path_prefixes
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_uvicorn_logging_configs(server_args=None):
|
||||||
from uvicorn.config import LOGGING_CONFIG
|
from uvicorn.config import LOGGING_CONFIG
|
||||||
|
|
||||||
LOGGING_CONFIG["formatters"]["default"][
|
LOGGING_CONFIG["formatters"]["default"][
|
||||||
@@ -2326,6 +2383,68 @@ def set_uvicorn_logging_configs():
|
|||||||
] = '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'
|
] = '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'
|
||||||
LOGGING_CONFIG["formatters"]["access"]["datefmt"] = "%Y-%m-%d %H:%M:%S"
|
LOGGING_CONFIG["formatters"]["access"]["datefmt"] = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
_configure_uvicorn_access_log_filter(LOGGING_CONFIG, server_args)
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_uvicorn_access_log_filter(
|
||||||
|
uvicorn_logging_config: dict, server_args=None
|
||||||
|
):
|
||||||
|
"""Configure uvicorn access log path filter into uvicorn LOGGING_CONFIG.
|
||||||
|
|
||||||
|
This optionally filters uvicorn access logs (e.g., suppress noisy /metrics polling).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uvicorn_logging_config: The dict-like LOGGING_CONFIG from uvicorn.
|
||||||
|
server_args: Parsed server args object that may contain:
|
||||||
|
- uvicorn_access_log_exclude_prefixes (list[str] | tuple[str] | None)
|
||||||
|
"""
|
||||||
|
# Optionally filter uvicorn access logs (e.g., suppress noisy /metrics polling).
|
||||||
|
if server_args is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
filter_name = "sglang_uvicorn_access_path_filter"
|
||||||
|
|
||||||
|
excluded_prefixes = getattr(
|
||||||
|
server_args, "uvicorn_access_log_exclude_prefixes", None
|
||||||
|
)
|
||||||
|
if not excluded_prefixes:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Normalize: accept list/tuple; treat a single string as one prefix (not an iterable of chars).
|
||||||
|
if isinstance(excluded_prefixes, str):
|
||||||
|
excluded_prefixes = [excluded_prefixes]
|
||||||
|
|
||||||
|
# De-duplicate while keeping order; drop empty prefixes.
|
||||||
|
excluded_prefixes = [p for p in excluded_prefixes if p]
|
||||||
|
excluded_prefixes = list(dict.fromkeys(excluded_prefixes))
|
||||||
|
if not excluded_prefixes:
|
||||||
|
return
|
||||||
|
|
||||||
|
uvicorn_logging_config.setdefault("filters", {})
|
||||||
|
uvicorn_logging_config["filters"][filter_name] = {
|
||||||
|
"()": "sglang.srt.utils.common.UvicornAccessLogFilter",
|
||||||
|
"excluded_path_prefixes": excluded_prefixes,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attach filter to access handler and/or uvicorn.access logger (best-effort across uvicorn versions).
|
||||||
|
handlers = uvicorn_logging_config.get("handlers", {})
|
||||||
|
if "access" in handlers:
|
||||||
|
filters_list = handlers["access"].setdefault("filters", [])
|
||||||
|
if not isinstance(filters_list, list):
|
||||||
|
filters_list = list(filters_list)
|
||||||
|
handlers["access"]["filters"] = filters_list
|
||||||
|
if filter_name not in filters_list:
|
||||||
|
filters_list.append(filter_name)
|
||||||
|
|
||||||
|
loggers_cfg = uvicorn_logging_config.get("loggers", {})
|
||||||
|
if "uvicorn.access" in loggers_cfg:
|
||||||
|
filters_list = loggers_cfg["uvicorn.access"].setdefault("filters", [])
|
||||||
|
if not isinstance(filters_list, list):
|
||||||
|
filters_list = list(filters_list)
|
||||||
|
loggers_cfg["uvicorn.access"]["filters"] = filters_list
|
||||||
|
if filter_name not in filters_list:
|
||||||
|
filters_list.append(filter_name)
|
||||||
|
|
||||||
|
|
||||||
def get_open_port() -> int:
|
def get_open_port() -> int:
|
||||||
port = os.getenv("SGLANG_PORT")
|
port = os.getenv("SGLANG_PORT")
|
||||||
|
|||||||
Reference in New Issue
Block a user