[Bugfix][Logging] Preserve application log record factories (#58747)

Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: OpenAI Codex <noreply@openai.com>

Misha Goin committed Sep 28, 2026 at 09:20 UTC d7f5722d7b487dd6794f638043375a95ebecfef7
2 files changed +60 -13
tests/test_logger.py
+41
@@ -165,6 +165,45 @@ def test_json_logging(monkeypatch, tmp_path):
165 assert log["process"] == os.getpid()
166
167
168 +@pytest.mark.parametrize("factory_order", ["before", "after", "replacement"])
169 +def test_configure_logging_preserves_application_record_factory(
170 + monkeypatch, factory_order
171 +):
172 + """Application fields survive initial configuration and reconfiguration."""
173 + original_factory = logging.getLogRecordFactory()
174 + monkeypatch.setattr(vllm_logger, "dictConfig", lambda _: None)
175 + monkeypatch.setattr(vllm_logger, "_last_configured_logging_config", None)
176 + monkeypatch.setattr(vllm_logger, "_vllm_process_info", None)
177 + config = LoggingConfig()
178 + formatter = logging.Formatter("%(request_id)s %(vllm_process_name)s %(message)s")
179 +
180 + try:
181 + logging.setLogRecordFactory(logging.LogRecord)
182 + if factory_order != "before":
183 + configure_logging(config)
184 + base_factory = (
185 + logging.LogRecord
186 + if factory_order == "replacement"
187 + else logging.getLogRecordFactory()
188 + )
189 +
190 + def application_factory(*args, **kwargs):
191 + record = base_factory(*args, **kwargs)
192 + record.request_id = "request-123"
193 + return record
194 +
195 + logging.setLogRecordFactory(application_factory)
196 + decorate_logs("Worker_DP0")
197 + for _ in range(2):
198 + configure_logging(config)
199 + record = logging.getLogger("application").makeRecord(
200 + "application", logging.INFO, __file__, 1, "probe", (), None
201 + )
202 + assert formatter.format(record) == "request-123 Worker_DP0 probe"
203 + finally:
204 + logging.setLogRecordFactory(original_factory)
205 +
206 +
207 def test_use_color_force_color(monkeypatch):
208 """FORCE_COLOR forces colored logs without a TTY, while NO_COLOR and an
209 explicit VLLM_LOGGING_COLOR=0 take precedence over it."""
@@ -223,9 +262,11 @@ def test_logger_configuring_can_be_disabled(monkeypatch):
262 monkeypatch.setenv("VLLM_CONFIGURE_LOGGING", "0")
263 monkeypatch.delenv("VLLM_LOGGING_CONFIG_PATH", raising=False)
264
265 + original_factory = logging.getLogRecordFactory()
266 with patch("vllm.logger.dictConfig") as dict_config_mock:
267 configure_logging(LoggingConfig(configure_logging=False))
268 dict_config_mock.assert_not_called()
269 + assert logging.getLogRecordFactory() is original_factory
270
271
272 def test_an_error_is_raised_when_custom_logging_config_file_does_not_exist(monkeypatch):
vllm/logger.py
+19 -13
@@ -7,7 +7,7 @@ import json
7 import logging
8 import os
9 import sys
10 -from collections.abc import Generator, Hashable
10 +from collections.abc import Callable, Generator, Hashable
11 from contextlib import contextmanager
12 from copy import deepcopy
13 from dataclasses import replace
@@ -31,20 +31,24 @@ _FORMAT = (
31 )
32 _DATE_FORMAT = "%m-%d %H:%M:%S"
33
34 -_base_log_record_factory = logging.getLogRecordFactory()
34 _vllm_process_info: tuple[str, int] | None = None
35
36
38 -def _vllm_log_record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
39 - """Add vLLM process metadata to each log record."""
40 - record = _base_log_record_factory(*args, **kwargs)
41 - process_info = _vllm_process_info
42 - pid = os.getpid()
43 - if process_info is None or process_info[1] != pid:
44 - record.vllm_process_name = record.processName
45 - else:
46 - record.vllm_process_name = process_info[0]
47 - return record
37 +class _VllmLogRecordFactory:
38 + """Add vLLM process metadata while preserving the wrapped record factory."""
39 +
40 + def __init__(self, factory: Callable[..., logging.LogRecord]) -> None:
41 + self._factory = factory
42 +
43 + def __call__(self, *args: Any, **kwargs: Any) -> logging.LogRecord:
44 + record = self._factory(*args, **kwargs)
45 + process_info = _vllm_process_info
46 + pid = os.getpid()
47 + if process_info is None or process_info[1] != pid:
48 + record.vllm_process_name = record.processName
49 + else:
50 + record.vllm_process_name = process_info[0]
51 + return record
52
53
54 def set_vllm_process_name(process_name: str, *, skip_if_set: bool = False) -> None:
@@ -220,7 +224,9 @@ def _configure_vllm_root_logger(config: "LoggingConfig | None" = None) -> None:
224 )
225
226 if configure_logging:
223 - logging.setLogRecordFactory(_vllm_log_record_factory)
227 + factory = logging.getLogRecordFactory()
228 + if not isinstance(factory, _VllmLogRecordFactory):
229 + logging.setLogRecordFactory(_VllmLogRecordFactory(factory))
230 logging_config = deepcopy(DEFAULT_LOGGING_CONFIG)
231
232 vllm_handler = logging_config["handlers"]["vllm"]