Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions vllm/entrypoints/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project

import asyncio
import logging
import signal
import socket
from http import HTTPStatus
Expand All @@ -24,6 +25,36 @@
logger = init_logger(__name__)


class UvicornAccessLogFilter(logging.Filter):
"""Filter to exclude specific paths from uvicorn access logs.

This is useful for filtering out noisy endpoints like /health and /metrics
that are frequently polled by monitoring systems.
"""

def __init__(self, exclude_paths: list[str]) -> None:
super().__init__()
self.exclude_paths = set(exclude_paths)

def filter(self, record: logging.LogRecord) -> bool:
# Uvicorn access log format: '%(client_addr)s - "%(request_line)s" %(status_code)s' # noqa: E501
# The request line contains the method, path, and protocol.
# Example: '127.0.0.1:12345 - "GET /metrics HTTP/1.1" 200'
if hasattr(record, "scope"):
path = record.scope.get("path", "") # type: ignore[union-attr]
if path in self.exclude_paths:
return False
# Fallback: check the message for the path
# The path appears after the HTTP method (GET, POST, etc.)
# Pattern: 'METHOD /path ' or 'METHOD /path"'
message = record.getMessage()
for path in self.exclude_paths:
# Match patterns like: "GET /metrics " or "POST /health HTTP"
if f" {path} " in message or f' {path}"' in message:
return False
return True
Comment on lines +39 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The filter method has two issues that can lead to incorrect log filtering:

  1. Incorrect Fall-through Logic: When a log record has a scope attribute, the path is correctly extracted. However, if this path is not in exclude_paths, the code incorrectly falls through to the string-based fallback logic instead of immediately returning True. The scope attribute should be treated as the source of truth, and the fallback should only be engaged if scope is absent.

  2. Fragile Fallback for Query Parameters: The fallback logic, which parses the log message as a plain string, does not correctly handle URL query parameters. For instance, a request to /health?foo=bar will not be filtered even if /health is specified in exclude_paths, because the string matching for " /health " will fail.

I suggest refactoring the filter method to fix these issues. The proposed change ensures that if scope is present, a definitive filtering decision is made. If not, it uses a more robust fallback mechanism that correctly parses the path from the request line, even with query parameters.

    def filter(self, record: logging.LogRecord) -> bool:
        # Uvicorn access log format: '%(client_addr)s - "%(request_line)s" %(status_code)s'  # noqa: E501
        # The request line contains the method, path, and protocol.
        # Example: '127.0.0.1:12345 - "GET /metrics HTTP/1.1" 200'
        if hasattr(record, "scope"):
            path = record.scope.get("path", "")  # type: ignore[union-attr]
            # The `path` from scope is the source of truth.
            # If it's not in exclude_paths, we should log it.
            return path not in self.exclude_paths

        # Fallback: check the message for the path
        # The request line is typically quoted: "METHOD /path?query HTTP/1.1"
        # We'll look for the path within the quotes.
        message = record.getMessage()
        try:
            request_line = message.split('"')[1]
            path_with_query = request_line.split(" ", 2)[1]
            path = path_with_query.split("?", 1)[0]
            if path in self.exclude_paths:
                return False
        except IndexError:
            # If the message format is unexpected, we can't reliably filter.
            # It's safer to log than to incorrectly filter.
            pass
        return True



async def serve_http(
app: FastAPI,
sock: socket.socket | None,
Expand Down Expand Up @@ -51,12 +82,28 @@ async def serve_http(
)
h11_max_header_count = uvicorn_kwargs.pop("h11_max_header_count", None)

# Extract access log exclude paths if present
access_log_exclude_paths: list[str] = uvicorn_kwargs.pop(
"access_log_exclude_paths", []
)

# Set safe defaults if not provided
if h11_max_incomplete_event_size is None:
h11_max_incomplete_event_size = H11_MAX_INCOMPLETE_EVENT_SIZE_DEFAULT
if h11_max_header_count is None:
h11_max_header_count = H11_MAX_HEADER_COUNT_DEFAULT

# Apply access log filter if paths are specified
if access_log_exclude_paths:
uvicorn_access_logger = logging.getLogger("uvicorn.access")
uvicorn_access_logger.addFilter(
UvicornAccessLogFilter(access_log_exclude_paths)
)
logger.info(
"Uvicorn access log will exclude paths: %s",
", ".join(access_log_exclude_paths),
)

config = uvicorn.Config(app, **uvicorn_kwargs)
# Set header limits
config.h11_max_incomplete_event_size = h11_max_incomplete_event_size
Expand Down
1 change: 1 addition & 0 deletions vllm/entrypoints/openai/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2142,6 +2142,7 @@ async def run_server_worker(
# NOTE: When the 'disable_uvicorn_access_log' value is True,
# no access log will be output.
access_log=not args.disable_uvicorn_access_log,
access_log_exclude_paths=args.uvicorn_access_log_exclude_paths,
timeout_keep_alive=envs.VLLM_HTTP_TIMEOUT_KEEP_ALIVE,
ssl_keyfile=args.ssl_keyfile,
ssl_certfile=args.ssl_certfile,
Expand Down
4 changes: 4 additions & 0 deletions vllm/entrypoints/openai/cli_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ class FrontendArgs:
"""Log level for uvicorn."""
disable_uvicorn_access_log: bool = False
"""Disable uvicorn access log."""
uvicorn_access_log_exclude_paths: list[str] = field(default_factory=lambda: [])
"""Paths to exclude from uvicorn access log. For example, to exclude
/health and /metrics endpoints: --uvicorn-access-log-exclude-paths /health
--uvicorn-access-log-exclude-paths /metrics"""
allow_credentials: bool = False
"""Allow credentials."""
allowed_origins: list[str] = field(default_factory=lambda: ["*"])
Expand Down