[Core][Logging] Add built-in JSON formatter (#58739)
Add `--logging-config.formatter=json` to enable the python-json-logger formatter without having to use a complete Python logging dictConfig. Signed-off-by: Mark McLoughlin <markmc@redhat.com> Co-authored-by: OpenAI Codex <noreply@openai.com>
Mark McLoughlin committed
Oct 1, 2026 at 09:11 UTC
91fcec12350aa01cd151d866fd76a307df17d120
11 files changed
+148
-46
.buildkite/test_areas/misc.yaml
+2
-2
@@ -530,7 +530,7 @@ steps:
530
- vllm/v1/
531
- vllm/version.py
532
- cmake/utils.cmake
533
- - tests/test_access_log_filter.py
533
+ - tests/test_uvicorn_logging.py
534
- tests/test_attention_backend_registry.py
535
- tests/test_audio_media_size_precheck.py
536
- tests/test_cmake_utils.py
@@ -571,7 +571,7 @@ steps:
571
- pip install cmake # test_cmake_utils.py shells out to cmake; the CPU image has none
572
- python3 standalone_tests/lazy_imports.py
573
- pytest -v -s device_allocator
574
- - pytest -v -s test_access_log_filter.py
574
+ - pytest -v -s test_uvicorn_logging.py
575
- pytest -v -s test_attention_backend_registry.py
576
- pytest -v -s test_audio_media_size_precheck.py
577
- pytest -v -s test_cmake_utils.py
examples/features/logging_configuration.md
+53
-17
@@ -6,6 +6,7 @@ robust and flexible configuration of the various loggers used by vLLM.
6
For `vllm serve`, configure logging with CLI arguments:
7
8
- Use the built-in configuration, optionally with `--log-level`.
9
+- Emit structured JSON with `--logging-config.formatter json`.
10
- Use a custom Python logging configuration file with
11
`--logging-config.pylogging_config_file`.
12
- Disable vLLM logging configuration with
@@ -14,7 +15,7 @@ For `vllm serve`, configure logging with CLI arguments:
15
## CLI logging configuration
16
17
`--logging-config` accepts a JSON object. Its fields are `log_level`,
17
-`configure_logging`, and `pylogging_config_file`. For example:
18
+`formatter`, `configure_logging`, and `pylogging_config_file`. For example:
19
20
```bash
21
vllm serve mistralai/Mistral-7B-v0.1 \
@@ -28,6 +29,25 @@ vllm serve mistralai/Mistral-7B-v0.1 \
29
--logging-config.log_level DEBUG
30
```
31
32
+The built-in `text` formatter is selected by default. To emit one JSON object
33
+per vLLM and Uvicorn log record, select the built-in `json` formatter:
34
+
35
+```bash
36
+vllm serve mistralai/Mistral-7B-v0.1 \
37
+ --logging-config.formatter json
38
+```
39
+
40
+The built-in JSON output includes `asctime`, `levelname`, `name`,
41
+`processName`, `process`, `message`, and `vllm_process_name`. `processName` is
42
+Python's process name, `vllm_process_name` identifies vLLM's logical process
43
+(including worker ranks where applicable), and `process` is the
44
+operating-system PID.
45
+
46
+The built-in profile formats records emitted through vLLM's Python loggers and
47
+Uvicorn's server and access loggers when using `vllm serve`. It does not convert
48
+direct writes to `stdout` or `stderr`. Use a custom configuration to format
49
+other loggers.
50
+
51
`--log-level` is a shortcut for `--logging-config.log_level` and takes
52
precedence if both are supplied. It sets the level of vLLM's built-in logging
53
configuration. `--log-config-file` is deprecated and will be removed in
@@ -41,11 +61,11 @@ The custom logging configuration file must be JSON following Python's [logging
61
configuration dictionary
62
schema](https://docs.python.org/3/library/logging.config.html#dictionary-schema-details).
63
44
-!!! note "Custom configurations override `--log-level`"
64
+!!! note "Custom configurations override built-in formatter settings"
65
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
66
+ its built-in `dictConfig`; it does not merge the two. Therefore, `log_level`
67
+ and `formatter` apply only when no custom configuration file is provided.
68
+ Set levels and formatters in the custom file itself. vLLM applies the
69
resolved configuration in its child processes as well.
70
71
## Environment variables
@@ -62,10 +82,11 @@ for those settings.
82
83
### Example 1: Customize vLLM root logger
84
65
-For this example, we will customize the vLLM root logger to use
85
+For more control over fields and handlers, configure the vLLM root logger and
86
+Uvicorn's server and access loggers with
87
[`python-json-logger`](https://github.com/nhairs/python-json-logger)
67
-(which is part of the container image) to log to
68
-STDOUT of the console in JSON format with a log level of `INFO`.
88
+(which is part of the container image) to log to STDOUT in JSON format at
89
+`INFO` level.
90
91
To begin, first, create an appropriate JSON logging configuration file:
92
@@ -92,8 +113,24 @@ To begin, first, create an appropriate JSON logging configuration file:
113
"handlers": ["console"],
114
"level": "INFO",
115
"propagate": false
116
+ },
117
+ "uvicorn": {
118
+ "handlers": ["console"],
119
+ "level": "INFO",
120
+ "propagate": false
121
+ },
122
+ "uvicorn.error": {
123
+ "handlers": ["console"],
124
+ "level": "INFO",
125
+ "propagate": false
126
+ },
127
+ "uvicorn.access": {
128
+ "handlers": ["console"],
129
+ "level": "INFO",
130
+ "propagate": false
131
}
132
},
133
+ "disable_existing_loggers": false,
134
"version": 1
135
}
136
```
@@ -105,21 +142,20 @@ vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048 \
142
--logging-config.pylogging_config_file /path/to/logging_config.json
143
```
144
108
-Each vLLM log record is one JSON object and includes `vllm_process_name`, which
109
-identifies vLLM's logical process (including worker ranks where applicable).
110
-The standard `process` record attribute contains the operating-system PID.
111
-vLLM avoids altering `stdout` or `stderr`, so no text is prepended to JSON
112
-log records.
145
+With this custom configuration, each vLLM and Uvicorn log record is one JSON
146
+object. The example selects `vllm_process_name` and `process` explicitly; both
147
+fields are also present in the built-in JSON output described above. vLLM
148
+avoids altering `stdout` or `stderr`, so no text is prepended to JSON log
149
+records.
150
151
This applies to records emitted through the configured Python loggers. A JSON
152
formatter cannot convert unrelated output into JSON, such as a third-party
153
library writing directly to `stdout` or `stderr`; configure or route such
154
output separately when a consumer requires every collected line to be JSON.
155
119
-When serving, `VLLM_LOGGING_CONFIG_PATH` is also used as Uvicorn's logging
120
-configuration. This example configures only `vllm`; configure `uvicorn`,
121
-`uvicorn.error`, and `uvicorn.access` with a JSON handler when those server
122
-logs must also be structured.
156
+When serving, vLLM also passes this file to Uvicorn as its logging
157
+configuration. The `uvicorn`, `uvicorn.error`, and `uvicorn.access` entries
158
+format its server and access logs as JSON.
159
160
### Example 2: Silence a particular vLLM logger
161
tests/entrypoints/launchers/test_cli_args.py
+3
@@ -76,6 +76,8 @@ def test_logging_config_cli_args(serve_parser):
76
[
77
"--logging-config",
78
'{"log_level":"WARNING","pylogging_config_file":"/tmp/json.json"}',
79
+ "--logging-config.formatter",
80
+ "json",
81
"--log-level",
82
"DEBUG",
83
"--log-config-file",
@@ -85,6 +87,7 @@ def test_logging_config_cli_args(serve_parser):
87
88
config = AsyncEngineArgs.from_cli_args(args).create_logging_config()
89
assert config.log_level == "DEBUG"
90
+ assert config.formatter == "json"
91
assert config.pylogging_config_file == "/tmp/flat.json"
92
93
tests/test_logger.py
+31
@@ -4,6 +4,7 @@ import enum
4
import io
5
import json
6
import logging
7
+import multiprocessing
8
import os
9
import sys
10
import tempfile
@@ -21,6 +22,7 @@ from vllm.config import LoggingConfig
22
from vllm.logger import (
23
_DATE_FORMAT,
24
_FORMAT,
25
+ _JSON_FORMAT,
26
_configure_vllm_root_logger,
27
_use_color,
28
configure_logging,
@@ -204,6 +206,35 @@ def test_configure_logging_preserves_application_record_factory(
206
logging.setLogRecordFactory(original_factory)
207
208
209
+def test_builtin_json_formatter(monkeypatch):
210
+ output = io.StringIO()
211
+
212
+ try:
213
+ with monkeypatch.context() as context:
214
+ context.setattr(sys, "stdout", output)
215
+ context.setattr(vllm_logger, "_vllm_process_info", None)
216
+ _configure_vllm_root_logger(LoggingConfig(formatter="json"))
217
+ decorate_logs("Worker_DP0")
218
+ init_logger("vllm.structured_log_probe").info("structured log probe")
219
+
220
+ log = json.loads(output.getvalue())
221
+ formatter = logging.getLogger("vllm").handlers[0].formatter
222
+ finally:
223
+ _configure_vllm_root_logger(LoggingConfig())
224
+
225
+ assert log == {
226
+ "asctime": log["asctime"],
227
+ "levelname": "INFO",
228
+ "name": "vllm.structured_log_probe",
229
+ "processName": multiprocessing.current_process().name,
230
+ "process": os.getpid(),
231
+ "message": "structured log probe",
232
+ "vllm_process_name": "Worker_DP0",
233
+ }
234
+ assert formatter is not None
235
+ assert formatter._fmt == _JSON_FORMAT
236
+
237
+
238
def test_use_color_force_color(monkeypatch):
239
"""FORCE_COLOR forces colored logs without a TTY, while NO_COLOR and an
240
explicit VLLM_LOGGING_COLOR=0 take precedence over it."""
tests/test_uvicorn_logging.py
renamed
+2
-2
@@ -1,10 +1,10 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Tests for the UvicornAccessLogFilter class."""
3
+"""Tests for Uvicorn logging configuration and access log filtering."""
4
5
import logging
6
7
-from vllm.logging_utils.access_log_filter import (
7
+from vllm.logging_utils.uvicorn_logging import (
8
UvicornAccessLogFilter,
9
create_uvicorn_log_config,
10
)
vllm/config/logging.py
+8
-4
@@ -9,6 +9,7 @@ import vllm.envs as envs
9
from vllm.config.utils import config
10
11
LogLevel = Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"]
12
+LogFormatter = Literal["text", "json"]
13
14
15
@config
@@ -17,8 +18,8 @@ class LoggingConfig:
18
19
Supply a JSON object with ``--logging-config`` or individual fields with
20
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
21
+ ``--logging-config.formatter json``. ``--log-level`` and legacy
22
+ ``--log-config-file`` override their matching fields. Set
23
``configure_logging`` to false to skip applying a ``dictConfig``.
24
"""
25
@@ -27,6 +28,9 @@ class LoggingConfig:
28
)
29
"""Log level used when no custom logging configuration is provided."""
30
31
+ formatter: LogFormatter = "text"
32
+ """Formatter used by the built-in logging configuration."""
33
+
34
configure_logging: bool = Field(default_factory=lambda: envs.VLLM_CONFIGURE_LOGGING)
35
"""Whether to apply a Python logging configuration.
36
@@ -41,6 +45,6 @@ class LoggingConfig:
45
"""Path to a Python logging JSON file using
46
[``dictConfig`` schema](https://docs.python.org/3/library/logging.config.html#configuration-file-format).
47
44
- A custom ``dictConfig`` is authoritative over ``log_level`` for logger,
45
- handler, and formatter settings.
48
+ A custom ``dictConfig`` is authoritative over ``log_level`` and
49
+ ``formatter`` for logger, handler, and formatter settings.
50
"""
vllm/entrypoints/launchers/utils/server_utils.py
+9
-6
@@ -8,6 +8,7 @@ from contextlib import asynccontextmanager
8
from fastapi import FastAPI
9
10
from vllm import envs
11
+from vllm.config.logging import LogFormatter
12
from vllm.engine.protocol import EngineClient
13
from vllm.logger import init_logger
14
from vllm.utils.gc_utils import freeze_gc_heap
@@ -33,8 +34,8 @@ def get_uvicorn_log_config(args: Namespace) -> dict | None:
34
35
Priority:
36
1. If log_config_file is specified, use it
36
- 2. If disable_access_log_for_endpoints is specified, create a config with
37
- the access log filter
37
+ 2. If disable_access_log_for_endpoints or JSON formatting is specified,
38
+ create a config with the requested filter and format
39
3. Otherwise, return None (use uvicorn defaults)
40
"""
41
# First, try to load from file if specified
@@ -48,19 +49,21 @@ def get_uvicorn_log_config(args: Namespace) -> dict | None:
49
if log_config is not None:
50
return log_config
51
51
- # If endpoints to filter are specified, create a config with the filter
52
- if args.disable_access_log_for_endpoints:
52
+ formatter: LogFormatter = (
53
+ logging_config.formatter if logging_config is not None else "text"
54
+ )
55
+ if args.disable_access_log_for_endpoints or formatter == "json":
56
from vllm.logging_utils import create_uvicorn_log_config
57
55
- # Parse comma-separated string into list
58
excluded_paths = [
59
p.strip()
58
- for p in args.disable_access_log_for_endpoints.split(",")
60
+ for p in (args.disable_access_log_for_endpoints or "").split(",")
61
if p.strip()
62
]
63
return create_uvicorn_log_config(
64
excluded_paths=excluded_paths,
65
log_level=args.uvicorn_log_level,
66
+ formatter=formatter,
67
)
68
69
return None
vllm/logger.py
+16
-4
@@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Any, Literal, cast
20
21
import vllm.envs as envs
22
from vllm.logging_utils import ColoredFormatter, NewLineFormatter
23
+from vllm.logging_utils.formatter import JSON_FORMAT as _JSON_FORMAT
24
25
if TYPE_CHECKING:
26
from vllm.config.logging import LoggingConfig
@@ -30,7 +31,6 @@ _FORMAT = (
31
"[%(fileinfo)s:%(lineno)d] %(message)s"
32
)
33
_DATE_FORMAT = "%m-%d %H:%M:%S"
33
-
34
_vllm_process_info: tuple[str, int] | None = None
35
36
@@ -211,10 +211,12 @@ def _configure_vllm_root_logger(config: "LoggingConfig | None" = None) -> None:
211
configure_logging = envs.VLLM_CONFIGURE_LOGGING
212
log_level = envs.VLLM_LOGGING_LEVEL
213
log_config_file = envs.VLLM_LOGGING_CONFIG_PATH
214
+ log_formatter = "text"
215
else:
216
configure_logging = config.configure_logging
217
log_level = config.log_level
218
log_config_file = config.pylogging_config_file
219
+ log_formatter = config.formatter
220
221
if not configure_logging and log_config_file:
222
raise RuntimeError(
@@ -233,12 +235,22 @@ def _configure_vllm_root_logger(config: "LoggingConfig | None" = None) -> None:
235
# Refresh these values in case env vars have changed.
236
vllm_handler["level"] = log_level
237
vllm_handler["stream"] = envs.VLLM_LOGGING_STREAM
236
- vllm_handler["formatter"] = "vllm_color" if _use_color() else "vllm"
238
+ for formatter in logging_config["formatters"].values():
239
+ formatter["log_level"] = log_level
240
+
241
+ if log_formatter == "json":
242
+ logging_config["formatters"] = {
243
+ "vllm_json": {
244
+ "class": "pythonjsonlogger.jsonlogger.JsonFormatter",
245
+ "format": _JSON_FORMAT,
246
+ }
247
+ }
248
+ vllm_handler["formatter"] = "vllm_json"
249
+ else:
250
+ vllm_handler["formatter"] = "vllm_color" if _use_color() else "vllm"
251
252
vllm_loggers = logging_config["loggers"]["vllm"]
253
vllm_loggers["level"] = log_level
240
- for formatter in logging_config["formatters"].values():
241
- formatter["log_level"] = log_level
254
255
if log_config_file:
256
if not path.exists(log_config_file):
vllm/logging_utils/__init__.py
+4
-4
@@ -1,14 +1,14 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-from vllm.logging_utils.access_log_filter import (
5
- UvicornAccessLogFilter,
6
- create_uvicorn_log_config,
7
-)
4
from vllm.logging_utils.formatter import ColoredFormatter, NewLineFormatter
5
from vllm.logging_utils.lazy import lazy
6
from vllm.logging_utils.log_time import logtime
7
from vllm.logging_utils.torch_tensor import tensors_str_no_data
8
+from vllm.logging_utils.uvicorn_logging import (
9
+ UvicornAccessLogFilter,
10
+ create_uvicorn_log_config,
11
+)
12
13
__all__ = [
14
"NewLineFormatter",
vllm/logging_utils/formatter.py
+4
@@ -4,6 +4,10 @@
4
import logging
5
from pathlib import Path
6
7
+JSON_FORMAT = (
8
+ "%(asctime)s %(levelname)s %(name)s %(processName)s %(process)d %(message)s"
9
+)
10
+
11
12
class NewLineFormatter(logging.Formatter):
13
"""Adds logging prefix to newlines to align multi-line messages."""
vllm/logging_utils/uvicorn_logging.py
renamed
+16
-7
@@ -1,15 +1,13 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Access log filter for uvicorn to exclude specific endpoints from logging.
4
-
5
-This module provides a logging filter that can be used to suppress access logs
6
-for specific endpoints (e.g., /health, /metrics) to reduce log noise in
7
-production environments.
8
-"""
3
+"""Uvicorn logging configuration and access log filtering."""
4
5
import logging
6
+from typing import Literal
7
from urllib.parse import urlparse
8
9
+from vllm.logging_utils.formatter import JSON_FORMAT
10
+
11
12
class UvicornAccessLogFilter(logging.Filter):
13
"""A logging filter that excludes access logs for specified endpoint paths.
@@ -70,8 +68,9 @@ class UvicornAccessLogFilter(logging.Filter):
68
def create_uvicorn_log_config(
69
excluded_paths: list[str] | None = None,
70
log_level: str = "info",
71
+ formatter: Literal["text", "json"] = "text",
72
) -> dict:
74
- """Create a uvicorn logging configuration with access log filtering.
73
+ """Create a uvicorn logging configuration with optional access filtering.
74
75
This function generates a logging configuration dictionary that can be
76
passed to uvicorn's `log_config` parameter. It sets up the access log
@@ -80,6 +79,7 @@ def create_uvicorn_log_config(
79
Args:
80
excluded_paths: List of URL paths to exclude from access logs.
81
log_level: The log level for uvicorn loggers.
82
+ formatter: Format used for uvicorn logs.
83
84
Returns:
85
A dictionary containing the logging configuration.
@@ -140,4 +140,13 @@ def create_uvicorn_log_config(
140
},
141
},
142
}
143
+ if formatter == "json":
144
+ json_formatter = {
145
+ "class": "pythonjsonlogger.jsonlogger.JsonFormatter",
146
+ "format": JSON_FORMAT,
147
+ }
148
+ config["formatters"] = {
149
+ "default": json_formatter.copy(),
150
+ "access": json_formatter.copy(),
151
+ }
152
return config