[Core] Model console logging as CLI configuration (#57205)

Add `--logging-config` CLI argument which can be supplied as JSON or using dotted arguments. The `--log-level` argument is provided for convenience, and `--log-config-file` is deprecated in favor of `--logging-config.pylogging_config_file`. Signed-off-by: Mark McLoughlin <markmc@redhat.com> Co-authored-by: AI Assistant <noreply@openai.com>

Mark McLoughlin committed Sep 25, 2026 at 11:36 UTC 45d56c0baf454f2816af514011ae27c2857ee312
36 files changed +440 -135
examples/features/logging_configuration.md
+57 -43
@@ -3,46 +3,60 @@
3 vLLM leverages Python's `logging.config.dictConfig` functionality to enable
4 robust and flexible configuration of the various loggers used by vLLM.
5
6 -vLLM offers two environment variables that can be used to accommodate a range
7 -of logging configurations that range from simple-and-inflexible to
8 -more-complex-and-more-flexible.
6 +For `vllm serve`, configure logging with CLI arguments:
7
10 -- No vLLM logging (simple and inflexible)
11 - - Set `VLLM_CONFIGURE_LOGGING=0` (leaving `VLLM_LOGGING_CONFIG_PATH` unset)
12 -- vLLM's default logging configuration (simple and inflexible)
13 - - Leave `VLLM_CONFIGURE_LOGGING` unset or set `VLLM_CONFIGURE_LOGGING=1`
14 -- Fine-grained custom logging configuration (more complex, more flexible)
15 - - Leave `VLLM_CONFIGURE_LOGGING` unset or set `VLLM_CONFIGURE_LOGGING=1` and
16 - set `VLLM_LOGGING_CONFIG_PATH=<path-to-logging-config.json>`
8 +- Use the built-in configuration, optionally with `--log-level`.
9 +- Use a custom Python logging configuration file with
10 + `--logging-config.pylogging_config_file`.
11 +- Disable vLLM logging configuration with
12 + `--logging-config.configure_logging false`.
13
18 -## Logging Configuration Environment Variables
14 +## CLI logging configuration
15
20 -### `VLLM_CONFIGURE_LOGGING`
16 +`--logging-config` accepts a JSON object. Its fields are `log_level`,
17 +`configure_logging`, and `pylogging_config_file`. For example:
18
22 -`VLLM_CONFIGURE_LOGGING` controls whether or not vLLM takes any action to
23 -configure the loggers used by vLLM. This functionality is enabled by default,
24 -but can be disabled by setting `VLLM_CONFIGURE_LOGGING=0` when running vLLM.
19 +```bash
20 +vllm serve mistralai/Mistral-7B-v0.1 \
21 + --logging-config '{"log_level":"DEBUG","configure_logging":true}'
22 +```
23 +
24 +Fields can also be set individually with dotted arguments:
25
26 -If `VLLM_CONFIGURE_LOGGING` is enabled and no value is given for
27 -`VLLM_LOGGING_CONFIG_PATH`, vLLM will use built-in default configuration to
28 -configure the root vLLM logger. By default, no other vLLM loggers are
29 -configured and, as such, all vLLM loggers defer to the root vLLM logger to make
30 -all logging decisions.
26 +```bash
27 +vllm serve mistralai/Mistral-7B-v0.1 \
28 + --logging-config.log_level DEBUG
29 +```
30
32 -If `VLLM_CONFIGURE_LOGGING` is disabled and a value is given for
33 -`VLLM_LOGGING_CONFIG_PATH`, an error will occur while starting vLLM.
31 +`--log-level` is a shortcut for `--logging-config.log_level` and takes
32 +precedence if both are supplied. It sets the level of vLLM's built-in logging
33 +configuration. `--log-config-file` is deprecated and will be removed in
34 +v0.33.0; use `--logging-config.pylogging_config_file` in new commands.
35
35 -### `VLLM_LOGGING_CONFIG_PATH`
36 +If `configure_logging` is `false`, vLLM does not apply a logging
37 +configuration. It cannot be combined with `pylogging_config_file`. This has
38 +the same effect as the legacy `VLLM_CONFIGURE_LOGGING=0` setting.
39
37 -`VLLM_LOGGING_CONFIG_PATH` allows users to specify a path to a JSON file of
38 -alternative, custom logging configuration that will be used instead of vLLM's
39 -built-in default logging configuration. The logging configuration should be
40 -provided in JSON format following the schema specified by Python's [logging
40 +The custom logging configuration file must be JSON following Python's [logging
41 configuration dictionary
42 schema](https://docs.python.org/3/library/logging.config.html#dictionary-schema-details).
43
44 -If `VLLM_LOGGING_CONFIG_PATH` is specified, but `VLLM_CONFIGURE_LOGGING` is
45 -disabled, an error will occur while starting vLLM.
44 +!!! note "Custom configurations override `--log-level`"
45 + When `pylogging_config_file` is set, vLLM loads that JSON file and replaces
46 + its built-in `dictConfig`; it does not merge the two. Therefore,
47 + `--log-level` applies only when no custom configuration file is provided.
48 + Set logger and handler levels in the custom file itself. vLLM applies the
49 + resolved configuration in its child processes as well.
50 +
51 +## Environment variables
52 +
53 +The CLI configuration is recommended for `vllm serve`. The legacy
54 +`VLLM_CONFIGURE_LOGGING`, `VLLM_LOGGING_LEVEL`, and
55 +`VLLM_LOGGING_CONFIG_PATH` variables remain supported as defaults. The default
56 +handler's stream, prefix, and color are currently controlled only through
57 +environment variables. Values from a YAML `--config` file or the command line
58 +override those defaults. See [Environment Variables](https://docs.vllm.ai/en/latest/configuration/env_vars/)
59 +for those settings.
60
61 ## Examples
62
@@ -83,12 +97,11 @@ To begin, first, create an appropriate JSON logging configuration file:
97 }
98 ```
99
86 -Finally, run vLLM with the `VLLM_LOGGING_CONFIG_PATH` environment variable set
87 -to the path of the custom logging configuration JSON file:
100 +Finally, run vLLM with the custom logging configuration JSON file:
101
102 ```bash
90 -VLLM_LOGGING_CONFIG_PATH=/path/to/logging_config.json \
91 - vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048
103 +vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048 \
104 + --logging-config.pylogging_config_file /path/to/logging_config.json
105 ```
106
107 ### Example 2: Silence a particular vLLM logger
@@ -137,26 +150,27 @@ configuration for the root vLLM logger and for the logger you wish to silence:
150 }
151 ```
152
140 -Finally, run vLLM with the `VLLM_LOGGING_CONFIG_PATH` environment variable set
141 -to the path of the custom logging configuration JSON file:
153 +Finally, run vLLM with the custom logging configuration JSON file:
154
155 ```bash
144 -VLLM_LOGGING_CONFIG_PATH=/path/to/logging_config.json \
145 - vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048
156 +vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048 \
157 + --logging-config.pylogging_config_file /path/to/logging_config.json
158 ```
159
160 ### Example 3: Disable vLLM default logging configuration
161
150 -To disable vLLM's default logging configuration and silence all vLLM loggers,
151 -simple set `VLLM_CONFIGURE_LOGGING=0` when running vLLM. This will prevent vLLM
152 -for configuring the root vLLM logger, which in turn, silences all other vLLM
153 -loggers.
162 +To disable vLLM's default logging configuration and silence vLLM log output,
163 +set `--logging-config.configure_logging false` when running vLLM. This prevents
164 +vLLM from configuring the root vLLM logger, which in turn silences other vLLM
165 +loggers unless the application or Python root logger configures a handler.
166
167 ```bash
156 -VLLM_CONFIGURE_LOGGING=0 \
157 - vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048
168 +vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048 \
169 + --logging-config.configure_logging false
170 ```
171
172 +For legacy launch scripts, `VLLM_CONFIGURE_LOGGING=0` has the same effect.
173 +
174 ### Example 4: Disable access logs for health check endpoints
175
176 In production environments, health check endpoints like `/health`, `/metrics`,
tests/conftest.py
+8 -4
@@ -1430,16 +1430,20 @@ def temporary_enable_log_propagate():
1430 import logging
1431
1432 logger = logging.getLogger("vllm")
1433 + previous_propagate = logger.propagate
1434 logger.propagate = True
1435 yield
1435 - logger.propagate = False
1436 + logger.propagate = previous_propagate
1437
1438
1439 @pytest.fixture()
1440 def caplog_vllm(temporary_enable_log_propagate, caplog):
1440 - # To capture vllm log, we should enable propagate=True temporarily
1441 - # because caplog depends on logs propagated to the root logger.
1442 - yield caplog
1441 + # caplog depends on logs propagated to the root logger. The vLLM logger
1442 + # inherits this INFO level until runtime initialization configures it.
1443 + import logging
1444 +
1445 + with caplog.at_level(logging.INFO):
1446 + yield caplog
1447
1448
1449 @pytest.fixture()
tests/entrypoints/launchers/test_cli_args.py
+19
@@ -8,6 +8,7 @@ import pytest
8
9 import vllm.entrypoints.launchers.cli_args as cli_args_module
10 from tests.utils import VLLM_PATH
11 +from vllm.engine.arg_utils import AsyncEngineArgs
12 from vllm.entrypoints.launchers.cli_args import (
13 make_arg_parser,
14 validate_parsed_serve_args,
@@ -69,6 +70,24 @@ def test_config_arg_parsing(serve_parser, cli_config_file):
70 assert args.port == 9000
71
72
73 +def test_logging_config_cli_args(serve_parser):
74 + with pytest.warns(UserWarning, match="--log-config-file is deprecated"):
75 + args = serve_parser.parse_args(
76 + [
77 + "--logging-config",
78 + '{"log_level":"WARNING","pylogging_config_file":"/tmp/json.json"}',
79 + "--log-level",
80 + "DEBUG",
81 + "--log-config-file",
82 + "/tmp/flat.json",
83 + ]
84 + )
85 +
86 + config = AsyncEngineArgs.from_cli_args(args).create_logging_config()
87 + assert config.log_level == "DEBUG"
88 + assert config.pylogging_config_file == "/tmp/flat.json"
89 +
90 +
91 ### Tests for LoRA module parsing
92 def test_valid_key_value_format(serve_parser):
93 # Test old format: name=path
tests/test_logger.py
+60 -9
@@ -16,11 +16,13 @@ from uuid import uuid4
16
17 import pytest
18
19 +from vllm.config import LoggingConfig
20 from vllm.logger import (
21 _DATE_FORMAT,
22 _FORMAT,
23 _configure_vllm_root_logger,
24 _use_color,
25 + configure_logging,
26 enable_trace_function_call,
27 init_logger,
28 )
@@ -50,16 +52,27 @@ def test_trace_function_call():
52 os.remove(path)
53
54
55 +def test_caplog_vllm_captures_info_before_runtime_logging_is_configured(caplog_vllm):
56 + message = "Capture this unconfigured INFO record"
57 + logger = init_logger(f"vllm.test_logger.{uuid4()}")
58 +
59 + logger.info(message)
60 +
61 + assert message in caplog_vllm.text
62 +
63 +
64 def test_default_vllm_root_logger_configuration(monkeypatch):
65 """This test presumes that VLLM_CONFIGURE_LOGGING (default: True) and
66 VLLM_LOGGING_CONFIG_PATH (default: None) are not configured and default
67 behavior is activated."""
68 monkeypatch.setenv("VLLM_LOGGING_COLOR", "0")
58 - _configure_vllm_root_logger()
69 + early_logger = init_logger(f"vllm.test_logger.{uuid4()}")
70 + configure_logging(LoggingConfig())
71
72 logger = logging.getLogger("vllm")
73 assert logger.level == logging.INFO
74 assert not logger.propagate
75 + assert early_logger.isEnabledFor(logging.INFO)
76
77 handler = logger.handlers[0]
78 assert isinstance(handler, logging.StreamHandler)
@@ -74,6 +87,29 @@ def test_default_vllm_root_logger_configuration(monkeypatch):
87 assert formatter.datefmt == _DATE_FORMAT
88
89
90 +def test_offline_llm_configures_logging_before_logging_args(monkeypatch):
91 + import vllm.entrypoints.llm as llm_module
92 +
93 + class StopInitialization(Exception):
94 + pass
95 +
96 + configured = False
97 +
98 + def configure_logging(_):
99 + nonlocal configured
100 + configured = True
101 +
102 + def log_args(_):
103 + assert configured
104 + raise StopInitialization
105 +
106 + monkeypatch.setattr(llm_module, "configure_logging_if_needed", configure_logging)
107 + monkeypatch.setattr(llm_module, "log_non_default_args", log_args)
108 +
109 + with pytest.raises(StopInitialization):
110 + llm_module.LLM(model="facebook/opt-125m")
111 +
112 +
113 def test_use_color_force_color(monkeypatch):
114 """FORCE_COLOR forces colored logs without a TTY, while NO_COLOR and an
115 explicit VLLM_LOGGING_COLOR=0 take precedence over it."""
@@ -133,7 +169,7 @@ def test_logger_configuring_can_be_disabled(monkeypatch):
169 monkeypatch.delenv("VLLM_LOGGING_CONFIG_PATH", raising=False)
170
171 with patch("vllm.logger.dictConfig") as dict_config_mock:
136 - _configure_vllm_root_logger()
172 + configure_logging(LoggingConfig(configure_logging=False))
173 dict_config_mock.assert_not_called()
174
175
@@ -214,9 +250,11 @@ def test_custom_logging_config_is_parsed_and_used_when_provided(monkeypatch):
250 with NamedTemporaryFile(encoding="utf-8", mode="w") as logging_config_file:
251 logging_config_file.write(json.dumps(valid_logging_config))
252 logging_config_file.flush()
217 - monkeypatch.setenv("VLLM_LOGGING_CONFIG_PATH", logging_config_file.name)
253 + monkeypatch.delenv("VLLM_LOGGING_CONFIG_PATH", raising=False)
254 with patch("vllm.logger.dictConfig") as dict_config_mock:
219 - _configure_vllm_root_logger()
255 + configure_logging(
256 + LoggingConfig(pylogging_config_file=logging_config_file.name)
257 + )
258 dict_config_mock.assert_called_with(valid_logging_config)
259
260
@@ -237,13 +275,18 @@ def test_custom_logging_config_causes_an_error_if_configure_logging_is_off(monke
275 with NamedTemporaryFile(encoding="utf-8", mode="w") as logging_config_file:
276 logging_config_file.write(json.dumps(valid_logging_config))
277 logging_config_file.flush()
240 - monkeypatch.setenv("VLLM_LOGGING_CONFIG_PATH", logging_config_file.name)
278 + monkeypatch.delenv("VLLM_LOGGING_CONFIG_PATH", raising=False)
279 with pytest.raises(RuntimeError) as ex_info:
242 - _configure_vllm_root_logger()
280 + configure_logging(
281 + LoggingConfig(
282 + configure_logging=False,
283 + pylogging_config_file=logging_config_file.name,
284 + )
285 + )
286 assert ex_info.type is RuntimeError
287 expected_message_snippet = (
245 - "VLLM_CONFIGURE_LOGGING evaluated to false, but "
246 - "VLLM_LOGGING_CONFIG_PATH was given."
288 + "Logging configuration is disabled, but a Python logging config "
289 + "file was given."
290 )
291 assert expected_message_snippet in str(ex_info)
292
@@ -297,6 +340,8 @@ test_logger = init_logger("vllm.test_logger")
340
341 def mp_function(**kwargs):
342 # This function runs in a subprocess
343 + if logging_config := kwargs.pop("logging_config", None):
344 + configure_logging(logging_config)
345
346 test_logger.warning("This is a subprocess: %s", kwargs.get("a"))
347 test_logger.error("This is a subprocess error.")
@@ -328,7 +373,13 @@ def test_caplog_mp_spawn(caplog_mp_spawn):
373 p = ctx.Process(
374 target=mp_function,
375 name=f"SubProcess{1}",
331 - kwargs={"a": "AAAA", "b": "BBBBB"},
376 + kwargs={
377 + "a": "AAAA",
378 + "b": "BBBBB",
379 + "logging_config": LoggingConfig(
380 + pylogging_config_file=os.environ["VLLM_LOGGING_CONFIG_PATH"]
381 + ),
382 + },
383 )
384 p.start()
385 p.join()
tests/v1/engine/test_async_llm.py
+3 -1
@@ -11,7 +11,7 @@ import pytest
11 import vllm.v1.engine.async_llm as async_llm_module
12 from vllm import SamplingParams
13 from vllm.assets.image import ImageAsset
14 -from vllm.config import VllmConfig
14 +from vllm.config import LoggingConfig, VllmConfig
15 from vllm.engine.arg_utils import AsyncEngineArgs
16 from vllm.entrypoints.openai.chat_completion.protocol import (
17 ChatCompletionRequest,
@@ -89,6 +89,7 @@ def _mock_async_llm_dependencies(monkeypatch: pytest.MonkeyPatch):
89
90 def test_cuda_profiler_requests_reach_engine_core(monkeypatch: pytest.MonkeyPatch):
91 vllm_config = MagicMock()
92 + vllm_config.logging_config = LoggingConfig()
93 vllm_config.observability_config.otlp_traces_endpoint = None
94 vllm_config.scheduler_config.stream_interval = 1
95 vllm_config.profiler_config.profiler = "cuda"
@@ -111,6 +112,7 @@ def test_cuda_only_torch_profiler_skips_frontend_cpu_trace(
112 monkeypatch: pytest.MonkeyPatch,
113 ):
114 vllm_config = MagicMock()
115 + vllm_config.logging_config = LoggingConfig()
116 vllm_config.observability_config.otlp_traces_endpoint = None
117 vllm_config.scheduler_config.stream_interval = 1
118 vllm_config.profiler_config.profiler = "torch"
vllm/compilation/breakable_cudagraph.py
+2 -1
@@ -25,6 +25,7 @@ from __future__ import annotations
25 import dataclasses
26 import functools
27 import gc
28 +import logging
29 import threading
30 import weakref
31 from collections.abc import Callable
@@ -285,7 +286,7 @@ class BreakableCUDAGraphWrapper:
286 self.runtime_mode = runtime_mode
287 self.compilation_config = vllm_config.compilation_config
288 self.graph_pool = current_platform.get_global_graph_pool()
288 - self.is_debugging_mode = envs.VLLM_LOGGING_LEVEL == "DEBUG"
289 + self.is_debugging_mode = logger.isEnabledFor(logging.DEBUG)
290
291 self.entries: dict[BatchDescriptor, _BreakableEntry] = {}
292 BreakableCUDAGraphWrapper._all_instances.add(self)
vllm/compilation/cuda_graph.py
+3 -3
@@ -2,6 +2,7 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import dataclasses
5 +import logging
6 import weakref
7 from collections import Counter
8 from collections.abc import Callable
@@ -11,7 +12,6 @@ from unittest.mock import patch
12
13 import torch
14
14 -import vllm.envs as envs
15 from vllm.compilation.counter import compilation_counter
16 from vllm.compilation.monitor import validate_cudagraph_capturing_enabled
17 from vllm.config import CUDAGraphMode, VllmConfig
@@ -164,7 +164,7 @@ class CUDAGraphWrapper:
164 assumption on the dynamic shape (batch size) of the runtime inputs, as a
165 trade-off for staying orthogonal to compilation logic. Nevertheless,
166 tracing and checking the input addresses to be consistent during replay is
167 - guaranteed when VLLM_LOGGING_LEVEL == "DEBUG".
167 + guaranteed when vLLM debug logging is enabled.
168 """
169
170 _all_instances: ClassVar[weakref.WeakSet["CUDAGraphWrapper"]] = weakref.WeakSet()
@@ -188,7 +188,7 @@ class CUDAGraphWrapper:
188 self.compilation_config = vllm_config.compilation_config
189
190 self.first_run_finished = False
191 - self.is_debugging_mode = envs.VLLM_LOGGING_LEVEL == "DEBUG"
191 + self.is_debugging_mode = logger.isEnabledFor(logging.DEBUG)
192 self._runnable_str = str(runnable) if self.is_debugging_mode else None
193
194 # assert runtime_mode is not NONE(no cudagraph), otherwise, we don't
vllm/config/__init__.py
+3
@@ -20,6 +20,7 @@ from vllm.config.kernel import KernelConfig
20 from vllm.config.kv_events import KVEventsConfig
21 from vllm.config.kv_transfer import KVTransferConfig
22 from vllm.config.load import LoadConfig
23 +from vllm.config.logging import LoggingConfig
24 from vllm.config.lora import LoRAConfig
25 from vllm.config.mamba import MambaConfig
26 from vllm.config.model import (
@@ -97,6 +98,8 @@ __all__ = [
98 "KVTransferConfig",
99 # From vllm.config.load
100 "LoadConfig",
101 + # From vllm.config.logging
102 + "LoggingConfig",
103 # From vllm.config.lora
104 "LoRAConfig",
105 # From vllm.config.mamba
vllm/config/logging.py new
+46
@@ -0,0 +1,46 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +from typing import Literal, cast
5 +
6 +from pydantic import Field
7 +
8 +import vllm.envs as envs
9 +from vllm.config.utils import config
10 +
11 +LogLevel = Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"]
12 +
13 +
14 +@config
15 +class LoggingConfig:
16 + """vLLM logging. Default ``dictConfig`` adds a formatted stream handler.
17 +
18 + Supply a JSON object with ``--logging-config`` or individual fields with
19 + dotted arguments such as ``--logging-config.log_level DEBUG`` and
20 + ``--logging-config.pylogging_config_file logging.json``. ``--log-level``
21 + and legacy ``--log-config-file`` override their matching fields. Set
22 + ``configure_logging`` to false to skip applying a ``dictConfig``.
23 + """
24 +
25 + log_level: LogLevel = Field(
26 + default_factory=lambda: cast(LogLevel, envs.VLLM_LOGGING_LEVEL)
27 + )
28 + """Log level used when no custom logging configuration is provided."""
29 +
30 + configure_logging: bool = Field(default_factory=lambda: envs.VLLM_CONFIGURE_LOGGING)
31 + """Whether to apply a Python logging configuration.
32 +
33 + With no ``pylogging_config_file``, the default config adds a formatted
34 + stream handler to the ``vllm`` logger. A custom Python logging configuration
35 + file also requires this to be enabled.
36 + """
37 +
38 + pylogging_config_file: str | None = Field(
39 + default_factory=lambda: envs.VLLM_LOGGING_CONFIG_PATH
40 + )
41 + """Path to a Python logging JSON file using
42 + [``dictConfig`` schema](https://docs.python.org/3/library/logging.config.html#configuration-file-format).
43 +
44 + A custom ``dictConfig`` is authoritative over ``log_level`` for logger,
45 + handler, and formatter settings.
46 + """
vllm/config/vllm.py
+3
@@ -40,6 +40,7 @@ from .kernel import KernelConfig
40 from .kv_events import KVEventsConfig
41 from .kv_transfer import KVTransferConfig
42 from .load import LoadConfig
43 +from .logging import LoggingConfig
44 from .lora import LoRAConfig
45 from .mamba import MambaBackendEnum, MambaConfig
46 from .model import ModelConfig
@@ -402,6 +403,8 @@ class VllmConfig:
403 default_factory=ObservabilityConfig
404 )
405 """Observability configuration."""
406 + logging_config: LoggingConfig = Field(default_factory=LoggingConfig)
407 + """Logging configuration."""
408 quant_config: QuantizationConfig | None = None
409 """Quantization configuration."""
410 compilation_config: CompilationConfig = Field(default_factory=CompilationConfig)
vllm/distributed/device_communicators/pynccl_allocator.py
+2 -1
@@ -2,6 +2,7 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import atexit
4 import contextlib
5 +import logging
6 import tempfile
7 from typing import Any
8
@@ -83,7 +84,7 @@ def compile_nccl_allocator():
84 cpp_sources=nccl_allocator_source,
85 with_cuda=True,
86 extra_ldflags=ldflags,
86 - verbose=envs.VLLM_LOGGING_LEVEL == "DEBUG",
87 + verbose=logger.isEnabledFor(logging.DEBUG),
88 is_python_module=False,
89 build_directory=out_dir,
90 extra_include_paths=nccl_include_paths,
vllm/engine/arg_utils.py
+64 -1
@@ -8,6 +8,7 @@ import functools
8 import json
9 import os
10 import sys
11 +import warnings
12 from collections.abc import Callable
13 from dataclasses import MISSING, asdict, dataclass, fields, is_dataclass
14 from types import UnionType
@@ -50,6 +51,7 @@ from vllm.config import (
51 KVEventsConfig,
52 KVTransferConfig,
53 LoadConfig,
54 + LoggingConfig,
55 LoRAConfig,
56 MambaConfig,
57 ModelConfig,
@@ -84,6 +86,7 @@ from vllm.config.kernel import (
86 SparseIndexerTopkBackend,
87 )
88 from vllm.config.load import SafetensorsLoadStrategy
89 +from vllm.config.logging import LogLevel
90 from vllm.config.lora import MaxLoRARanks
91 from vllm.config.mamba import MambaBackendEnum, MambaSSUAlgorithm
92 from vllm.config.model import (
@@ -113,7 +116,7 @@ from vllm.config.scheduler import SchedulerPolicy
116 from vllm.config.utils import get_field
117 from vllm.config.vllm import OptimizationLevel, PerformanceMode
118 from vllm.config.watermarking import WatermarkConfig
116 -from vllm.logger import init_logger, suppress_logging
119 +from vllm.logger import configure_logging_if_needed, init_logger, suppress_logging
120 from vllm.platforms import CpuArchEnum, current_platform
121 from vllm.plugins import load_general_plugins
122 from vllm.ray.lazy_utils import is_in_ray_actor, is_ray_initialized
@@ -443,6 +446,20 @@ def get_kwargs(cls: ConfigType) -> dict[str, dict[str, Any]]:
446 return copy.deepcopy(_compute_kwargs(cls))
447
448
449 +_LOG_CONFIG_FILE_DEPRECATION_MESSAGE = (
450 + "--log-config-file is deprecated and will be removed in v0.33.0. "
451 + "Use --logging-config.pylogging_config_file instead."
452 +)
453 +
454 +
455 +class DeprecatedLogConfigFileAction(argparse.Action):
456 + """Warn when the legacy ``--log-config-file`` option is used."""
457 +
458 + def __call__(self, parser, namespace, values, option_string=None):
459 + warnings.warn(_LOG_CONFIG_FILE_DEPRECATION_MESSAGE, stacklevel=1)
460 + setattr(namespace, self.dest, values)
461 +
462 +
463 @dataclass
464 class EngineArgs:
465 """Arguments for vLLM engine."""
@@ -733,6 +750,10 @@ class EngineArgs:
750
751 profiler_config: ProfilerConfig = get_field(VllmConfig, "profiler_config")
752
753 + logging_config: LoggingConfig | None = None
754 + log_level: LogLevel | None = None
755 + log_config_file: str | None = None
756 +
757 kv_transfer_config: KVTransferConfig | None = None
758 kv_events_config: KVEventsConfig | None = None
759
@@ -1551,6 +1572,34 @@ class EngineArgs:
1572 **lora_kwargs["enable_moe_shared_loras"],
1573 )
1574
1575 + # Logging arguments
1576 + logging_group = parser.add_argument_group(
1577 + title="LoggingConfig",
1578 + description=LoggingConfig.__doc__,
1579 + )
1580 + logging_config_kwargs = get_kwargs(VllmConfig)["logging_config"]
1581 + logging_config_kwargs["default"] = argparse.SUPPRESS
1582 + logging_group.add_argument("--logging-config", **logging_config_kwargs)
1583 + logging_group.add_argument(
1584 + "--log-level",
1585 + choices=get_args(LogLevel),
1586 + default=argparse.SUPPRESS,
1587 + help=(
1588 + "Shortcut for --logging-config.log_level. "
1589 + "Overrides that field if both are specified."
1590 + ),
1591 + )
1592 + logging_group.add_argument(
1593 + "--log-config-file",
1594 + dest="log_config_file",
1595 + default=argparse.SUPPRESS,
1596 + metavar="PYLOGGING_CONFIG_FILE",
1597 + action=DeprecatedLogConfigFileAction,
1598 + help=_LOG_CONFIG_FILE_DEPRECATION_MESSAGE,
1599 + )
1600 + # Retain the warning in v0.31.0 and v0.32.0. Remove this option and its
1601 + # compatibility mapping in create_logging_config() in v0.33.0.
1602 +
1603 # Observability arguments
1604 observability_kwargs = get_kwargs(ObservabilityConfig)
1605 observability_group = parser.add_argument_group(
@@ -2081,6 +2130,16 @@ class EngineArgs:
2130 jit_monitor_verbose=self.jit_monitor_verbose,
2131 )
2132
2133 + def create_logging_config(self) -> LoggingConfig:
2134 + config = self.logging_config or LoggingConfig()
2135 + if self.log_level is not None:
2136 + config = dataclasses.replace(config, log_level=self.log_level)
2137 + if self.log_config_file is not None:
2138 + config = dataclasses.replace(
2139 + config, pylogging_config_file=self.log_config_file
2140 + )
2141 + return config
2142 +
2143 def create_engine_config(
2144 self,
2145 usage_context: UsageContext | None = None,
@@ -2090,6 +2149,9 @@ class EngineArgs:
2149
2150 NOTE: If VllmConfig is incompatible, we raise an error.
2151 """
2152 + logging_config = self.create_logging_config()
2153 + configure_logging_if_needed(logging_config)
2154 +
2155 current_platform.pre_register_and_update()
2156
2157 device_config = DeviceConfig(device=cast(Device, current_platform.device_type))
@@ -2721,6 +2783,7 @@ class EngineArgs:
2783 diffusion_config=diffusion_config,
2784 structured_outputs_config=self.structured_outputs_config,
2785 observability_config=observability_config,
2786 + logging_config=logging_config,
2787 compilation_config=compilation_config,
2788 kv_transfer_config=self.kv_transfer_config,
2789 kv_events_config=self.kv_events_config,
vllm/entrypoints/cli/main.py
+4 -2
@@ -9,7 +9,7 @@ import importlib.metadata
9 import sys
10 from importlib.util import find_spec
11
12 -from vllm.logger import init_logger
12 +from vllm.logger import configure_logging_from_args, init_logger
13
14 logger = init_logger(__name__)
15
@@ -110,7 +110,9 @@ def main():
110 cmds[cmd.name] = cmd
111 args = parser.parse_args()
112 if args.subparser in cmds:
113 - cmds[args.subparser].validate(args)
113 + cmd = cmds[args.subparser]
114 + configure_logging_from_args(args)
115 + cmd.validate(args)
116
117 if hasattr(args, "dispatch_function"):
118 args.dispatch_function(args)
vllm/entrypoints/launchers/api_server/entry.py
+2 -1
@@ -15,7 +15,7 @@ from typing import Any
15 import vllm.envs as envs
16 from vllm.engine.arg_utils import AsyncEngineArgs
17 from vllm.engine.protocol import EngineClient
18 -from vllm.logger import init_logger
18 +from vllm.logger import configure_logging_from_args, init_logger
19 from vllm.reasoning import ReasoningParserManager
20 from vllm.tool_parsers import ToolParserManager
21 from vllm.usage.usage_lib import UsageContext
@@ -216,6 +216,7 @@ def main():
216 )
217 parser = make_arg_parser(parser)
218 args = parser.parse_args()
219 + configure_logging_from_args(args)
220 validate_parsed_serve_args(args)
221
222 uvloop.run(run_server(args))
vllm/entrypoints/launchers/cli_args.py
-2
@@ -134,8 +134,6 @@ class BaseFrontendArgs:
134 The `demo` Python tool executes model-generated code in Docker without
135 network isolation by default. See the security guide for more
136 information."""
137 - log_config_file: str | None = envs.VLLM_LOGGING_CONFIG_PATH
138 - """Path to logging config JSON file for both vllm and uvicorn"""
137 max_log_len: int | None = None
138 """Max number of prompt characters or prompt ID numbers being printed in
139 log. The default of None means unlimited."""
vllm/entrypoints/launchers/dp_supervisor.py
+4 -1
@@ -23,7 +23,7 @@ import uvloop
23 from fastapi import FastAPI, Response
24
25 import vllm.envs as envs
26 -from vllm.logger import init_logger
26 +from vllm.logger import configure_logging_from_args, init_logger
27 from vllm.utils.system_utils import (
28 decorate_logs,
29 kill_process_tree,
@@ -251,6 +251,8 @@ def _run_rust_vllm_dp_server(child_args: argparse.Namespace) -> None:
251
252 def _run_vllm_dp_server(child_args: argparse.Namespace) -> None:
253 """Entrypoint function for the vLLM DP Server."""
254 + configure_logging_from_args(child_args)
255 +
256 # Create a fresh process group for the vLLM DP Server,
257 # so that CTRL-C is propagated cleanly.
258 os.setpgrp()
@@ -558,4 +560,5 @@ class DPSupervisor:
560
561
562 def run_dp_supervisor(args: argparse.Namespace) -> None:
563 + configure_logging_from_args(args)
564 uvloop.run(DPSupervisor(args).run())
vllm/entrypoints/launchers/grpc_server.py
+2 -1
@@ -44,7 +44,7 @@ import uvloop
44 from vllm import envs
45 from vllm.engine.arg_utils import AsyncEngineArgs
46 from vllm.entrypoints.serve.utils.api_utils import log_version_and_model
47 -from vllm.logger import init_logger
47 +from vllm.logger import configure_logging_from_args, init_logger
48 from vllm.usage.usage_lib import UsageContext
49 from vllm.utils.argparse_utils import FlexibleArgumentParser
50 from vllm.v1.engine.async_llm import AsyncLLM
@@ -195,6 +195,7 @@ def main():
195 parser = AsyncEngineArgs.add_cli_args(parser)
196
197 args = parser.parse_args()
198 + configure_logging_from_args(args)
199
200 # Run server
201 try:
vllm/entrypoints/launchers/render/entry.py
+6 -2
@@ -8,7 +8,7 @@ from argparse import Namespace
8
9 from vllm import AsyncEngineArgs, envs
10 from vllm.config import VllmConfig
11 -from vllm.logger import init_logger
11 +from vllm.logger import configure_logging_from_args, init_logger
12
13 from ..app import build_app
14 from ..launcher import serve_http, setup_server
@@ -87,7 +87,10 @@ async def run_launch_fastapi(args: argparse.Namespace) -> None:
87 # cache space warning from CpuPlatform.check_and_update_config.
88 envs.VLLM_CPU_KVCACHE_SPACE = 0
89
90 - vllm_config = VllmConfig(model_config=model_config)
90 + vllm_config = VllmConfig(
91 + model_config=model_config,
92 + logging_config=engine_args.create_logging_config(),
93 + )
94 shutdown_task = await build_and_serve_renderer(
95 vllm_config, listen_address, sock, args
96 )
@@ -115,6 +118,7 @@ if __name__ == "__main__":
118 )
119 parser = make_arg_parser(parser)
120 args = parser.parse_args()
121 + configure_logging_from_args(args)
122 validate_parsed_serve_args(args)
123
124 uvloop.run(run_launch_fastapi(args))
vllm/entrypoints/launchers/run_batch.py
+2 -1
@@ -61,7 +61,7 @@ from vllm.entrypoints.speech_to_text.translation.protocol import (
61 TranslationResponseVerbose,
62 )
63 from vllm.exceptions import VLLMValidationError
64 -from vllm.logger import init_logger
64 +from vllm.logger import configure_logging_from_args, init_logger
65 from vllm.reasoning import ReasoningParserManager
66 from vllm.utils import random_uuid
67 from vllm.utils.argparse_utils import FlexibleArgumentParser
@@ -289,6 +289,7 @@ def make_arg_parser(parser: FlexibleArgumentParser):
289 def parse_args():
290 parser = FlexibleArgumentParser(description="vLLM OpenAI-Compatible batch runner.")
291 args = make_arg_parser(parser).parse_args()
292 + configure_logging_from_args(args)
293
294 # Backward compatibility: If --url is set, use it for host
295 url_explicit = any(arg == "--url" or arg.startswith("--url=") for arg in sys.argv)
vllm/entrypoints/launchers/utils/server_utils.py
+7 -1
@@ -38,7 +38,13 @@ def get_uvicorn_log_config(args: Namespace) -> dict | None:
38 3. Otherwise, return None (use uvicorn defaults)
39 """
40 # First, try to load from file if specified
41 - log_config = load_log_config(args.log_config_file)
41 + logging_config = getattr(args, "logging_config", None)
42 + log_config_file = (
43 + logging_config.pylogging_config_file
44 + if logging_config is not None
45 + else getattr(args, "log_config_file", None)
46 + )
47 + log_config = load_log_config(log_config_file)
48 if log_config is not None:
49 return log_config
50
vllm/entrypoints/llm.py
+2 -1
@@ -42,7 +42,7 @@ from vllm.entrypoints.generate.beam_search.offline import BeamSearchOfflineMixin
42 from vllm.entrypoints.pooling.offline import PoolingOfflineMixin
43 from vllm.entrypoints.serve.utils.api_utils import log_non_default_args
44 from vllm.inputs import PromptType
45 -from vllm.logger import init_logger
45 +from vllm.logger import configure_logging_if_needed, init_logger
46 from vllm.lora.request import LoRARequest
47 from vllm.model_executor.layers.quantization import QuantizationMethods
48 from vllm.outputs import PoolingRequestOutput, RequestOutput
@@ -338,6 +338,7 @@ class LLM(BeamSearchOfflineMixin, PoolingOfflineMixin, OfflineInferenceMixin):
338 **kwargs,
339 )
340
341 + configure_logging_if_needed(engine_args.create_logging_config())
342 log_non_default_args(engine_args)
343
344 self.llm_engine = LLMEngine.from_engine_args(
vllm/entrypoints/serve/utils/request_logger.py
+1 -1
@@ -31,7 +31,7 @@ class RequestLogger:
31 "`--enable-log-requests` is set but "
32 "the minimum log level is higher than DEBUG. "
33 "Only limited information will be logged to minimize overhead. "
34 - "To view more details, set `VLLM_LOGGING_LEVEL=DEBUG`."
34 + "To view more details, set `--log-level DEBUG`."
35 )
36
37 def log_inputs(
vllm/logger.py
+87 -33
@@ -9,16 +9,21 @@ import os
9 import sys
10 from collections.abc import Generator, Hashable
11 from contextlib import contextmanager
12 +from copy import deepcopy
13 +from dataclasses import replace
14 from functools import lru_cache, partial
15 from logging import Logger
16 from logging.config import dictConfig
17 from os import path
18 from types import MethodType
17 -from typing import Any, Literal, cast
19 +from typing import TYPE_CHECKING, Any, Literal, cast
20
21 import vllm.envs as envs
22 from vllm.logging_utils import ColoredFormatter, NewLineFormatter
23
24 +if TYPE_CHECKING:
25 + from vllm.config.logging import LoggingConfig
26 +
27 _FORMAT = (
28 f"{envs.VLLM_LOGGING_PREFIX}%(levelname)s %(asctime)s "
29 "[%(fileinfo)s:%(lineno)d] %(message)s"
@@ -40,15 +45,18 @@ def _use_color() -> bool:
45
46 DEFAULT_LOGGING_CONFIG: dict[str, dict[str, Any] | Any] = {
47 "formatters": {
48 + # The "()" factory form forwards custom kwargs such as log_level.
49 "vllm": {
44 - "class": "vllm.logging_utils.NewLineFormatter",
50 + "()": "vllm.logging_utils.NewLineFormatter",
51 "datefmt": _DATE_FORMAT,
52 "format": _FORMAT,
53 + "log_level": envs.VLLM_LOGGING_LEVEL,
54 },
55 "vllm_color": {
49 - "class": "vllm.logging_utils.ColoredFormatter",
56 + "()": "vllm.logging_utils.ColoredFormatter",
57 "datefmt": _DATE_FORMAT,
58 "format": _FORMAT,
59 + "log_level": envs.VLLM_LOGGING_LEVEL,
60 },
61 },
62 "handlers": {
@@ -71,6 +79,8 @@ DEFAULT_LOGGING_CONFIG: dict[str, dict[str, Any] | Any] = {
79 "disable_existing_loggers": False,
80 }
81
82 +_last_configured_logging_config: "LoggingConfig | None" = None
83 +
84
85 @lru_cache
86 def _print_debug_once(logger: Logger, msg: str, *args: Hashable) -> None:
@@ -165,36 +175,46 @@ _METHODS_TO_PATCH = {
175 }
176
177
168 -def _configure_vllm_root_logger() -> None:
178 +def _configure_vllm_root_logger(config: "LoggingConfig | None" = None) -> None:
179 + """Configure logging from explicit config or bootstrap environment values."""
180 logging_config: dict[str, dict[str, Any] | Any] = {}
170 -
171 - if not envs.VLLM_CONFIGURE_LOGGING and envs.VLLM_LOGGING_CONFIG_PATH:
181 + if config is None:
182 + configure_logging = envs.VLLM_CONFIGURE_LOGGING
183 + log_level = envs.VLLM_LOGGING_LEVEL
184 + log_config_file = envs.VLLM_LOGGING_CONFIG_PATH
185 + else:
186 + configure_logging = config.configure_logging
187 + log_level = config.log_level
188 + log_config_file = config.pylogging_config_file
189 +
190 + if not configure_logging and log_config_file:
191 raise RuntimeError(
173 - "VLLM_CONFIGURE_LOGGING evaluated to false, but "
174 - "VLLM_LOGGING_CONFIG_PATH was given. VLLM_LOGGING_CONFIG_PATH "
175 - "implies VLLM_CONFIGURE_LOGGING. Please enable "
176 - "VLLM_CONFIGURE_LOGGING or unset VLLM_LOGGING_CONFIG_PATH."
192 + "Logging configuration is disabled, but a Python logging config "
193 + "file was given. pylogging_config_file requires "
194 + "configure_logging to be enabled."
195 )
196
179 - if envs.VLLM_CONFIGURE_LOGGING:
180 - logging_config = DEFAULT_LOGGING_CONFIG
197 + if configure_logging:
198 + logging_config = deepcopy(DEFAULT_LOGGING_CONFIG)
199
200 vllm_handler = logging_config["handlers"]["vllm"]
201 # Refresh these values in case env vars have changed.
184 - vllm_handler["level"] = envs.VLLM_LOGGING_LEVEL
202 + vllm_handler["level"] = log_level
203 vllm_handler["stream"] = envs.VLLM_LOGGING_STREAM
204 vllm_handler["formatter"] = "vllm_color" if _use_color() else "vllm"
205
206 vllm_loggers = logging_config["loggers"]["vllm"]
189 - vllm_loggers["level"] = envs.VLLM_LOGGING_LEVEL
207 + vllm_loggers["level"] = log_level
208 + for formatter in logging_config["formatters"].values():
209 + formatter["log_level"] = log_level
210
191 - if envs.VLLM_LOGGING_CONFIG_PATH:
192 - if not path.exists(envs.VLLM_LOGGING_CONFIG_PATH):
211 + if log_config_file:
212 + if not path.exists(log_config_file):
213 raise RuntimeError(
214 "Could not load logging config. File does not exist: %s",
195 - envs.VLLM_LOGGING_CONFIG_PATH,
215 + log_config_file,
216 )
197 - with open(envs.VLLM_LOGGING_CONFIG_PATH, encoding="utf-8") as file:
217 + with open(log_config_file, encoding="utf-8") as file:
218 custom_config = json.loads(file.read())
219
220 if not isinstance(custom_config, dict):
@@ -212,11 +232,57 @@ def _configure_vllm_root_logger() -> None:
232 if logging_config:
233 dictConfig(logging_config)
234
235 + # Transformers uses httpx to access the Hugging Face Hub. httpx is quite verbose,
236 + # so we set its logging level to WARNING when vLLM's logging level is INFO.
237 + # httpx2 is the successor huggingface_hub switches to in its 2.x releases.
238 + if log_level == "INFO":
239 + logging.getLogger("httpx").setLevel(logging.WARNING)
240 + logging.getLogger("httpx2").setLevel(logging.WARNING)
241 +
242 +
243 +def configure_logging(config: "LoggingConfig") -> None:
244 + """Apply a logging configuration in the current process."""
245 + _configure_vllm_root_logger(config)
246 + global _last_configured_logging_config
247 + _last_configured_logging_config = config
248 + _log_platform_warnings(config)
249 +
250 +
251 +def _log_platform_warnings(config: "LoggingConfig") -> None:
252 + """Emit platform diagnostics only after an enabled config is active."""
253 + if not config.configure_logging:
254 + return
255 +
256 + # Import lazily because platform modules use init_logger during import.
257 + from vllm.platforms import current_platform
258 +
259 + current_platform.log_warnings()
260 +
261 +
262 +def configure_logging_if_needed(config: "LoggingConfig") -> None:
263 + """Apply a logging configuration unless it is already active in this process."""
264 + if config != _last_configured_logging_config:
265 + configure_logging(config)
266 +
267 +
268 +def configure_logging_from_args(args: Any) -> "LoggingConfig":
269 + """Apply parsed logging arguments and retain them for child processes."""
270 + from vllm.config.logging import LoggingConfig
271 +
272 + config = getattr(args, "logging_config", None) or LoggingConfig()
273 + if hasattr(args, "log_level"):
274 + config = replace(config, log_level=args.log_level)
275 + if hasattr(args, "log_config_file"):
276 + config = replace(config, pylogging_config_file=args.log_config_file)
277 +
278 + configure_logging_if_needed(config)
279 + args.log_config_file = config.pylogging_config_file
280 + args.logging_config = config
281 + return config
282 +
283
284 def init_logger(name: str) -> _VllmLogger:
217 - """The main purpose of this function is to ensure that loggers are
218 - retrieved in such a way that we can be sure the root vllm logger has
219 - already been configured."""
285 + """Retrieve a logger and add vLLM's convenience logging methods."""
286 logger = logging.getLogger(name)
287
288 for method_name, method in _METHODS_TO_PATCH.items():
@@ -246,18 +312,6 @@ def current_formatter_type(logger: Logger) -> Literal["color", "newline", None]:
312 return None
313
314
249 -# The root logger is initialized when the module is imported.
250 -# This is thread-safe as the module is only imported once,
251 -# guaranteed by the Python GIL.
252 -_configure_vllm_root_logger()
253 -
254 -# Transformers uses httpx to access the Hugging Face Hub. httpx is quite verbose,
255 -# so we set its logging level to WARNING when vLLM's logging level is INFO.
256 -# httpx2 is the successor huggingface_hub switches to in its 2.x releases.
257 -if envs.VLLM_LOGGING_LEVEL == "INFO":
258 - logging.getLogger("httpx").setLevel(logging.WARNING)
259 - logging.getLogger("httpx2").setLevel(logging.WARNING)
260 -
315 logger = init_logger(__name__)
316
317
vllm/logging_utils/formatter.py
+4 -6
@@ -4,16 +4,14 @@
4 import logging
5 from pathlib import Path
6
7 -from vllm import envs
8 -
7
8 class NewLineFormatter(logging.Formatter):
9 """Adds logging prefix to newlines to align multi-line messages."""
10
13 - def __init__(self, fmt, datefmt=None, style="%"):
11 + def __init__(self, fmt, datefmt=None, style="%", *, log_level: str | None = None):
12 super().__init__(fmt, datefmt, style)
13
16 - self.use_relpath = envs.VLLM_LOGGING_LEVEL == "DEBUG"
14 + self.use_relpath = log_level == "DEBUG"
15 if self.use_relpath:
16 self.root_dir = Path(__file__).resolve().parent.parent.parent
17
@@ -95,7 +93,7 @@ class ColoredFormatter(NewLineFormatter):
93 GREY = "\033[90m" # Grey for timestamp and file info
94 RESET = "\033[0m"
95
98 - def __init__(self, fmt, datefmt=None, style="%"):
96 + def __init__(self, fmt, datefmt=None, style="%", *, log_level: str | None = None):
97 # Inject grey color codes into format string for timestamp and file info
98 if fmt:
99 # Wrap %(asctime)s with grey
@@ -107,7 +105,7 @@ class ColoredFormatter(NewLineFormatter):
105 )
106
107 # Call parent __init__ with potentially modified format string
110 - super().__init__(fmt, datefmt, style)
108 + super().__init__(fmt, datefmt, style, log_level=log_level)
109
110 def format(self, record):
111 # Store original levelname to restore later (in case record is reused)
vllm/platforms/cuda.py
+2 -7
@@ -298,10 +298,6 @@ class CudaPlatformBase(Platform):
298 def is_fully_connected(cls, device_ids: list[int]) -> bool:
299 raise NotImplementedError
300
301 - @classmethod
302 - def log_warnings(cls):
303 - pass
304 -
301 @classmethod
302 def is_pin_memory_available(cls) -> bool:
303 if in_wsl():
@@ -1009,11 +1005,12 @@ class NvmlCudaPlatform(CudaPlatformBase):
1005 len(set(device_names)) > 1
1006 and os.environ.get("CUDA_DEVICE_ORDER") != "PCI_BUS_ID"
1007 ):
1012 - logger.warning(
1008 + logger.warning_once(
1009 "Detected different devices in the system: %s. Please"
1010 " make sure to set `CUDA_DEVICE_ORDER=PCI_BUS_ID` to "
1011 "avoid unexpected behavior.",
1012 ", ".join(device_names),
1013 + scope="process",
1014 )
1015
1016
@@ -1065,5 +1062,3 @@ finally:
1062 pynvml.nvmlShutdown()
1063
1064 CudaPlatform = NvmlCudaPlatform if nvml_available else NonNvmlCudaPlatform
1068 -
1069 -CudaPlatform.log_warnings()
vllm/platforms/interface.py
+4
@@ -147,6 +147,10 @@ class Platform:
147 # empty string means the device does not support ray
148 ray_device_key: str = ""
149
150 + @classmethod
151 + def log_warnings(cls) -> None:
152 + """Log platform-specific diagnostics after logging is configured."""
153 +
154 # platform-agnostic way to specify the device control environment variable,
155 # .e.g. CUDA_VISIBLE_DEVICES for CUDA.
156 # hint: search for "get_visible_accelerator_ids_env_var" in
vllm/utils/system_utils.py
+3 -2
@@ -4,6 +4,7 @@
4 from __future__ import annotations
5
6 import contextlib
7 +import logging
8 import multiprocessing
9 import os
10 import signal
@@ -63,7 +64,7 @@ def suppress_stdout():
64 """Suppress stdout from C libraries at the file descriptor level.
65
66 Only suppresses stdout, not stderr, to preserve error messages.
66 - Suppression is disabled when VLLM_LOGGING_LEVEL is set to DEBUG.
67 + Suppression is disabled when vLLM debug logging is enabled.
68
69 Example:
70 with suppress_stdout():
@@ -72,7 +73,7 @@ def suppress_stdout():
73
74 """
75 # Don't suppress if logging level is DEBUG
75 - if envs.VLLM_LOGGING_LEVEL == "DEBUG":
76 + if logger.isEnabledFor(logging.DEBUG):
77 yield
78 return
79
vllm/v1/engine/async_llm.py
+3 -1
@@ -28,7 +28,7 @@ from vllm.exceptions import (
28 VLLMValidationError,
29 )
30 from vllm.inputs import EngineInput, PromptType
31 -from vllm.logger import init_logger
31 +from vllm.logger import configure_logging_if_needed, init_logger
32 from vllm.lora.request import LoRARequest
33 from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
34 from vllm.outputs import STREAM_FINISHED, PoolingRequestOutput, RequestOutput
@@ -122,6 +122,8 @@ class AsyncLLM(EngineClient):
122 None
123
124 """
125 + configure_logging_if_needed(vllm_config.logging_config)
126 +
127 # Ensure we can serialize custom transformer configs
128 maybe_register_config_serialize_by_value()
129
vllm/v1/engine/coordinator.py
+11 -3
@@ -9,8 +9,8 @@ import weakref
9 import msgspec.msgpack
10 import zmq
11
12 -from vllm.config import ParallelConfig
13 -from vllm.logger import init_logger
12 +from vllm.config import LoggingConfig, ParallelConfig
13 +from vllm.logger import configure_logging, init_logger
14 from vllm.utils.network_utils import make_zmq_socket
15 from vllm.utils.system_utils import get_mp_context, set_process_title
16 from vllm.v1.engine import EngineCoreOutputs, EngineCoreRequestType
@@ -77,7 +77,10 @@ class DPCoordinator:
77 zmq_addr_pipe.close()
78
79 def __init__(
80 - self, parallel_config: ParallelConfig, enable_wave_coordination: bool = True
80 + self,
81 + parallel_config: ParallelConfig,
82 + enable_wave_coordination: bool = True,
83 + logging_config: LoggingConfig | None = None,
84 ):
85 dp_size = parallel_config.data_parallel_size
86 assert dp_size > 1, "Coordinator only used for data parallel"
@@ -108,6 +111,7 @@ class DPCoordinator:
111 "back_publish_address": back_publish_address,
112 "zmq_addr_pipe": child_zmq_addr_pipe,
113 "enable_wave_coordination": enable_wave_coordination,
114 + "logging_config": logging_config,
115 },
116 daemon=True,
117 )
@@ -167,7 +171,11 @@ class DPCoordinatorProc:
171 zmq_addr_pipe=None,
172 min_stats_update_interval_ms: int = 100,
173 enable_wave_coordination: bool = True,
174 + logging_config: LoggingConfig | None = None,
175 ):
176 + if logging_config is not None:
177 + configure_logging(logging_config)
178 +
179 coordinator = DPCoordinatorProc(
180 engine_count=engine_count,
181 min_stats_update_interval_ms=min_stats_update_interval_ms,
vllm/v1/engine/core.py
+8 -2
@@ -28,7 +28,7 @@ from vllm.distributed import (
28 stateless_destroy_torch_distributed_process_group,
29 )
30 from vllm.envs import enable_envs_cache
31 -from vllm.logger import init_logger
31 +from vllm.logger import configure_logging, init_logger
32 from vllm.logging_utils.dump_input import dump_engine_exception
33 from vllm.lora.request import LoRARequest
34 from vllm.multimodal.cache import (
@@ -1350,6 +1350,10 @@ class EngineCoreProc(EngineCore):
1350 @staticmethod
1351 def run_engine_core(*args, dp_rank: int = 0, local_dp_rank: int = 0, **kwargs):
1352 """Launch EngineCore busy loop in background process."""
1353 + vllm_config: VllmConfig = kwargs["vllm_config"]
1354 + if logging_config := getattr(vllm_config, "logging_config", None):
1355 + configure_logging(logging_config)
1356 +
1357 # Ensure we can serialize transformer config after spawning
1358 maybe_register_config_serialize_by_value()
1359
@@ -1357,7 +1361,6 @@ class EngineCoreProc(EngineCore):
1361 signal_callback: SignalCallback | None = None
1362 clean_shutdown = False
1363 try:
1360 - vllm_config: VllmConfig = kwargs["vllm_config"]
1364 parallel_config: ParallelConfig = vllm_config.parallel_config
1365 data_parallel = parallel_config.data_parallel_size > 1 or dp_rank > 0
1366 if data_parallel:
@@ -2473,6 +2476,9 @@ class EngineCoreActorMixin:
2476 dp_rank: int = 0,
2477 local_dp_rank: int = 0,
2478 ):
2479 + if logging_config := getattr(vllm_config, "logging_config", None):
2480 + configure_logging(logging_config)
2481 +
2482 # Initialize tracer for distributed tracing if configured.
2483 maybe_init_worker_tracer(
2484 instrumenting_module_name="vllm.engine_core",
vllm/v1/engine/llm_engine.py
+2 -1
@@ -16,7 +16,7 @@ from vllm.distributed import stateless_destroy_torch_distributed_process_group
16 from vllm.distributed.parallel_state import get_dp_group
17 from vllm.engine.arg_utils import EngineArgs
18 from vllm.inputs import EngineInput, PromptType
19 -from vllm.logger import init_logger
19 +from vllm.logger import configure_logging_if_needed, init_logger
20 from vllm.lora.request import LoRARequest
21 from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
22 from vllm.outputs import PoolingRequestOutput, RequestOutput
@@ -60,6 +60,7 @@ class LLMEngine:
60 mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,
61 multiprocess_mode: bool = False,
62 ) -> None:
63 + configure_logging_if_needed(vllm_config.logging_config)
64 self.vllm_config = vllm_config
65 self.model_config = vllm_config.model_config
66 self.observability_config = vllm_config.observability_config
vllm/v1/engine/utils.py
+1
@@ -1128,6 +1128,7 @@ def launch_core_engines(
1128 coordinator = DPCoordinator(
1129 parallel_config,
1130 enable_wave_coordination=vllm_config.model_config.is_moe,
1131 + logging_config=vllm_config.logging_config,
1132 )
1133
1134 addresses.coordinator_input, addresses.coordinator_output = (
vllm/v1/executor/multiproc_executor.py
+4 -1
@@ -42,7 +42,7 @@ from vllm.distributed.parallel_state import (
42 model_parallel_is_initialized,
43 )
44 from vllm.envs import enable_envs_cache
45 -from vllm.logger import init_logger
45 +from vllm.logger import configure_logging, init_logger
46 from vllm.platforms import current_platform
47 from vllm.tracing import instrument, maybe_init_worker_tracer
48 from vllm.utils import numa_utils
@@ -853,6 +853,9 @@ class WorkerProc:
853 def worker_main(*args, **kwargs):
854 """Worker initialization and execution loops.
855 This runs a background process"""
856 + if logging_config := getattr(kwargs["vllm_config"], "logging_config", None):
857 + configure_logging(logging_config)
858 +
859 # Signal handler used for graceful termination.
860 # SystemExit exception is only raised once to allow this and worker
861 # processes to terminate without error
vllm/v1/executor/ray_executor_v2.py
+4 -1
@@ -16,7 +16,7 @@ from vllm.distributed.device_communicators.shm_broadcast import (
16 Handle,
17 MessageQueue,
18 )
19 -from vllm.logger import init_logger
19 +from vllm.logger import configure_logging, init_logger
20 from vllm.platforms import current_platform
21 from vllm.utils.network_utils import (
22 get_distributed_init_method,
@@ -113,6 +113,9 @@ class RayWorkerProc(WorkerProc):
113 is_driver_worker: bool,
114 is_driver_node: bool = False,
115 ):
116 + if logging_config := getattr(vllm_config, "logging_config", None):
117 + configure_logging(logging_config)
118 +
119 # Defer WorkerProc.__init__ until GPU IDs are known.
120 self._is_driver_node = is_driver_node
121 self._parallel_config = vllm_config.parallel_config
vllm/v1/utils.py
+5
@@ -520,6 +520,11 @@ def run_api_server_worker_proc(
520 listen_address, sock, args, client_config=None, **uvicorn_kwargs
521 ) -> None:
522 """Entrypoint for individual API server worker processes."""
523 + if logging_config := getattr(args, "logging_config", None):
524 + from vllm.logger import configure_logging
525 +
526 + configure_logging(logging_config)
527 +
528 from vllm.entrypoints.launchers.api_server.entry import run_server_worker
529
530 client_config = client_config or {}
vllm/v1/worker/gpu_ubatch_wrapper.py
+2 -2
@@ -1,6 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 +import logging
5 import threading
6 from collections.abc import Callable
7 from dataclasses import dataclass
@@ -8,7 +9,6 @@ from typing import Any
9
10 import torch
11
11 -import vllm.envs as envs
12 from vllm.compilation.cuda_graph import CUDAGraphWrapper
13 from vllm.config import CUDAGraphMode, VllmConfig
14 from vllm.distributed.device_communicators.pynccl_allocator import set_graph_pool_id
@@ -89,7 +89,7 @@ class UBatchWrapper:
89
90 self.sm_control = create_sm_control_context(vllm_config.parallel_config)
91 self.device = device
92 - self.is_debugging_mode = envs.VLLM_LOGGING_LEVEL == "DEBUG"
92 + self.is_debugging_mode = logger.isEnabledFor(logging.DEBUG)
93 self._runnable_str = str(runnable) if self.is_debugging_mode else None
94
95 @property