A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 9, 14:45 UTC
[Logging] add `--disable-access-log-for-endpoints` CLI option (#30011)
Add a new CLI option --disable-access-log-for-endpoints to suppress uvicorn access logs for specified endpoints (e.g., /health, /metrics, /ping). This addresses the need to reduce log noise in production environments where health check endpoints are frequently polled by load balancers or monitoring systems, generating excessive log entries that obscure meaningful request logs. Fixes #29982 Signed-off-by: JaredforReal <w13431838023@gmail.com>
Jared Wen committed
Jan 27, 2026 at 05:49 UTC
6ee7f18f33bd83df657fa0a3aec9b448e71e4781
6 files changed
+598
-2
examples/others/logging_configuration.md
+31
@@ -157,6 +157,37 @@ VLLM_CONFIGURE_LOGGING=0 \
157
vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048
158
```
159
160
+### Example 4: Disable access logs for health check endpoints
161
+
162
+In production environments, health check endpoints like `/health`, `/metrics`,
163
+and `/ping` are frequently called by load balancers and monitoring systems,
164
+generating a large volume of repetitive access logs. To reduce log noise while
165
+keeping logs for other endpoints, use the `--disable-access-log-for-endpoints`
166
+option.
167
+
168
+**Disable access logs for health and metrics endpoints:**
169
+
170
+```bash
171
+vllm serve mistralai/Mistral-7B-v0.1 --max-model-len 2048 \
172
+ --disable-access-log-for-endpoints /health,/metrics,/ping
173
+```
174
+
175
+**Common endpoints to consider filtering:**
176
+
177
+| Endpoint | Description | Typical Caller |
178
+| ---------- | ---------------------- | ---------------------------------------------------- |
179
+| `/health` | Health check | Kubernetes liveness/readiness probes, load balancers |
180
+| `/metrics` | Prometheus metrics | Prometheus scraper (every 15-60s) |
181
+| `/ping` | SageMaker health check | SageMaker infrastructure |
182
+| `/load` | Server load metrics | Custom monitoring |
183
+
184
+**Notes:**
185
+
186
+- This option only affects uvicorn access logs, not vLLM application logs
187
+- Specify multiple endpoints by separating them with commas (no spaces)
188
+- The filter uses exact path matching, query parameters are ignored (e.g., `/health?verbose=true` matches `/health`)
189
+- If you need to completely disable all access logs, use `--disable-uvicorn-access-log` instead
190
+
191
## Additional resources
192
193
- [`logging.config` Dictionary Schema Details](https://docs.python.org/3/library/logging.config.html#dictionary-schema-details)
tests/test_access_log_filter.py
new
+371
@@ -0,0 +1,371 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""
4
+Tests for the UvicornAccessLogFilter class.
5
+"""
6
+
7
+import logging
8
+
9
+from vllm.logging_utils.access_log_filter import (
10
+ UvicornAccessLogFilter,
11
+ create_uvicorn_log_config,
12
+)
13
+
14
+
15
+class TestUvicornAccessLogFilter:
16
+ """Test cases for UvicornAccessLogFilter."""
17
+
18
+ def test_filter_allows_all_when_no_excluded_paths(self):
19
+ """Filter should allow all logs when no paths are excluded."""
20
+ filter = UvicornAccessLogFilter(excluded_paths=[])
21
+
22
+ record = logging.LogRecord(
23
+ name="uvicorn.access",
24
+ level=logging.INFO,
25
+ pathname="",
26
+ lineno=0,
27
+ msg='%s - "%s %s HTTP/%s" %d',
28
+ args=("127.0.0.1:12345", "GET", "/v1/completions", "1.1", 200),
29
+ exc_info=None,
30
+ )
31
+
32
+ assert filter.filter(record) is True
33
+
34
+ def test_filter_allows_all_when_excluded_paths_is_none(self):
35
+ """Filter should allow all logs when excluded_paths is None."""
36
+ filter = UvicornAccessLogFilter(excluded_paths=None)
37
+
38
+ record = logging.LogRecord(
39
+ name="uvicorn.access",
40
+ level=logging.INFO,
41
+ pathname="",
42
+ lineno=0,
43
+ msg='%s - "%s %s HTTP/%s" %d',
44
+ args=("127.0.0.1:12345", "GET", "/health", "1.1", 200),
45
+ exc_info=None,
46
+ )
47
+
48
+ assert filter.filter(record) is True
49
+
50
+ def test_filter_excludes_health_endpoint(self):
51
+ """Filter should exclude /health endpoint when configured."""
52
+ filter = UvicornAccessLogFilter(excluded_paths=["/health"])
53
+
54
+ record = logging.LogRecord(
55
+ name="uvicorn.access",
56
+ level=logging.INFO,
57
+ pathname="",
58
+ lineno=0,
59
+ msg='%s - "%s %s HTTP/%s" %d',
60
+ args=("127.0.0.1:12345", "GET", "/health", "1.1", 200),
61
+ exc_info=None,
62
+ )
63
+
64
+ assert filter.filter(record) is False
65
+
66
+ def test_filter_excludes_metrics_endpoint(self):
67
+ """Filter should exclude /metrics endpoint when configured."""
68
+ filter = UvicornAccessLogFilter(excluded_paths=["/metrics"])
69
+
70
+ record = logging.LogRecord(
71
+ name="uvicorn.access",
72
+ level=logging.INFO,
73
+ pathname="",
74
+ lineno=0,
75
+ msg='%s - "%s %s HTTP/%s" %d',
76
+ args=("127.0.0.1:12345", "GET", "/metrics", "1.1", 200),
77
+ exc_info=None,
78
+ )
79
+
80
+ assert filter.filter(record) is False
81
+
82
+ def test_filter_allows_non_excluded_endpoints(self):
83
+ """Filter should allow endpoints not in the excluded list."""
84
+ filter = UvicornAccessLogFilter(excluded_paths=["/health", "/metrics"])
85
+
86
+ record = logging.LogRecord(
87
+ name="uvicorn.access",
88
+ level=logging.INFO,
89
+ pathname="",
90
+ lineno=0,
91
+ msg='%s - "%s %s HTTP/%s" %d',
92
+ args=("127.0.0.1:12345", "POST", "/v1/completions", "1.1", 200),
93
+ exc_info=None,
94
+ )
95
+
96
+ assert filter.filter(record) is True
97
+
98
+ def test_filter_excludes_multiple_endpoints(self):
99
+ """Filter should exclude multiple configured endpoints."""
100
+ filter = UvicornAccessLogFilter(excluded_paths=["/health", "/metrics", "/ping"])
101
+
102
+ # Test /health
103
+ record_health = logging.LogRecord(
104
+ name="uvicorn.access",
105
+ level=logging.INFO,
106
+ pathname="",
107
+ lineno=0,
108
+ msg='%s - "%s %s HTTP/%s" %d',
109
+ args=("127.0.0.1:12345", "GET", "/health", "1.1", 200),
110
+ exc_info=None,
111
+ )
112
+ assert filter.filter(record_health) is False
113
+
114
+ # Test /metrics
115
+ record_metrics = logging.LogRecord(
116
+ name="uvicorn.access",
117
+ level=logging.INFO,
118
+ pathname="",
119
+ lineno=0,
120
+ msg='%s - "%s %s HTTP/%s" %d',
121
+ args=("127.0.0.1:12345", "GET", "/metrics", "1.1", 200),
122
+ exc_info=None,
123
+ )
124
+ assert filter.filter(record_metrics) is False
125
+
126
+ # Test /ping
127
+ record_ping = logging.LogRecord(
128
+ name="uvicorn.access",
129
+ level=logging.INFO,
130
+ pathname="",
131
+ lineno=0,
132
+ msg='%s - "%s %s HTTP/%s" %d',
133
+ args=("127.0.0.1:12345", "GET", "/ping", "1.1", 200),
134
+ exc_info=None,
135
+ )
136
+ assert filter.filter(record_ping) is False
137
+
138
+ def test_filter_with_query_parameters(self):
139
+ """Filter should exclude endpoints even with query parameters."""
140
+ filter = UvicornAccessLogFilter(excluded_paths=["/health"])
141
+
142
+ record = logging.LogRecord(
143
+ name="uvicorn.access",
144
+ level=logging.INFO,
145
+ pathname="",
146
+ lineno=0,
147
+ msg='%s - "%s %s HTTP/%s" %d',
148
+ args=("127.0.0.1:12345", "GET", "/health?verbose=true", "1.1", 200),
149
+ exc_info=None,
150
+ )
151
+
152
+ assert filter.filter(record) is False
153
+
154
+ def test_filter_different_http_methods(self):
155
+ """Filter should exclude endpoints regardless of HTTP method."""
156
+ filter = UvicornAccessLogFilter(excluded_paths=["/ping"])
157
+
158
+ # Test GET
159
+ record_get = logging.LogRecord(
160
+ name="uvicorn.access",
161
+ level=logging.INFO,
162
+ pathname="",
163
+ lineno=0,
164
+ msg='%s - "%s %s HTTP/%s" %d',
165
+ args=("127.0.0.1:12345", "GET", "/ping", "1.1", 200),
166
+ exc_info=None,
167
+ )
168
+ assert filter.filter(record_get) is False
169
+
170
+ # Test POST
171
+ record_post = logging.LogRecord(
172
+ name="uvicorn.access",
173
+ level=logging.INFO,
174
+ pathname="",
175
+ lineno=0,
176
+ msg='%s - "%s %s HTTP/%s" %d',
177
+ args=("127.0.0.1:12345", "POST", "/ping", "1.1", 200),
178
+ exc_info=None,
179
+ )
180
+ assert filter.filter(record_post) is False
181
+
182
+ def test_filter_with_different_status_codes(self):
183
+ """Filter should exclude endpoints regardless of status code."""
184
+ filter = UvicornAccessLogFilter(excluded_paths=["/health"])
185
+
186
+ for status_code in [200, 500, 503]:
187
+ record = logging.LogRecord(
188
+ name="uvicorn.access",
189
+ level=logging.INFO,
190
+ pathname="",
191
+ lineno=0,
192
+ msg='%s - "%s %s HTTP/%s" %d',
193
+ args=("127.0.0.1:12345", "GET", "/health", "1.1", status_code),
194
+ exc_info=None,
195
+ )
196
+ assert filter.filter(record) is False
197
+
198
+
199
+class TestCreateUvicornLogConfig:
200
+ """Test cases for create_uvicorn_log_config function."""
201
+
202
+ def test_creates_valid_config_structure(self):
203
+ """Config should have required logging configuration keys."""
204
+ config = create_uvicorn_log_config(excluded_paths=["/health"])
205
+
206
+ assert "version" in config
207
+ assert config["version"] == 1
208
+ assert "disable_existing_loggers" in config
209
+ assert "formatters" in config
210
+ assert "handlers" in config
211
+ assert "loggers" in config
212
+ assert "filters" in config
213
+
214
+ def test_config_includes_access_log_filter(self):
215
+ """Config should include the access log filter."""
216
+ config = create_uvicorn_log_config(excluded_paths=["/health", "/metrics"])
217
+
218
+ assert "access_log_filter" in config["filters"]
219
+ filter_config = config["filters"]["access_log_filter"]
220
+ assert filter_config["()"] == UvicornAccessLogFilter
221
+ assert filter_config["excluded_paths"] == ["/health", "/metrics"]
222
+
223
+ def test_config_applies_filter_to_access_handler(self):
224
+ """Config should apply the filter to the access handler."""
225
+ config = create_uvicorn_log_config(excluded_paths=["/health"])
226
+
227
+ assert "access" in config["handlers"]
228
+ assert "filters" in config["handlers"]["access"]
229
+ assert "access_log_filter" in config["handlers"]["access"]["filters"]
230
+
231
+ def test_config_with_custom_log_level(self):
232
+ """Config should respect custom log level."""
233
+ config = create_uvicorn_log_config(
234
+ excluded_paths=["/health"], log_level="debug"
235
+ )
236
+
237
+ assert config["loggers"]["uvicorn"]["level"] == "DEBUG"
238
+ assert config["loggers"]["uvicorn.access"]["level"] == "DEBUG"
239
+ assert config["loggers"]["uvicorn.error"]["level"] == "DEBUG"
240
+
241
+ def test_config_with_empty_excluded_paths(self):
242
+ """Config should work with empty excluded paths."""
243
+ config = create_uvicorn_log_config(excluded_paths=[])
244
+
245
+ assert config["filters"]["access_log_filter"]["excluded_paths"] == []
246
+
247
+ def test_config_with_none_excluded_paths(self):
248
+ """Config should work with None excluded paths."""
249
+ config = create_uvicorn_log_config(excluded_paths=None)
250
+
251
+ assert config["filters"]["access_log_filter"]["excluded_paths"] == []
252
+
253
+
254
+class TestIntegration:
255
+ """Integration tests for the access log filter."""
256
+
257
+ def test_filter_with_real_logger(self):
258
+ """Test filter works with a real Python logger simulating uvicorn."""
259
+ # Create a logger with our filter (simulating uvicorn.access)
260
+ logger = logging.getLogger("uvicorn.access")
261
+ logger.setLevel(logging.INFO)
262
+
263
+ # Clear any existing handlers
264
+ logger.handlers = []
265
+
266
+ # Create a custom handler that tracks messages
267
+ logged_messages: list[str] = []
268
+
269
+ class TrackingHandler(logging.Handler):
270
+ def emit(self, record):
271
+ logged_messages.append(record.getMessage())
272
+
273
+ handler = TrackingHandler()
274
+ handler.setLevel(logging.INFO)
275
+ filter = UvicornAccessLogFilter(excluded_paths=["/health", "/metrics"])
276
+ handler.addFilter(filter)
277
+ logger.addHandler(handler)
278
+
279
+ # Log using uvicorn's format with args tuple
280
+ # Format: '%s - "%s %s HTTP/%s" %d'
281
+ logger.info(
282
+ '%s - "%s %s HTTP/%s" %d',
283
+ "127.0.0.1:12345",
284
+ "GET",
285
+ "/health",
286
+ "1.1",
287
+ 200,
288
+ )
289
+ logger.info(
290
+ '%s - "%s %s HTTP/%s" %d',
291
+ "127.0.0.1:12345",
292
+ "GET",
293
+ "/v1/completions",
294
+ "1.1",
295
+ 200,
296
+ )
297
+ logger.info(
298
+ '%s - "%s %s HTTP/%s" %d',
299
+ "127.0.0.1:12345",
300
+ "GET",
301
+ "/metrics",
302
+ "1.1",
303
+ 200,
304
+ )
305
+ logger.info(
306
+ '%s - "%s %s HTTP/%s" %d',
307
+ "127.0.0.1:12345",
308
+ "POST",
309
+ "/v1/chat/completions",
310
+ "1.1",
311
+ 200,
312
+ )
313
+
314
+ # Verify only non-excluded endpoints were logged
315
+ assert len(logged_messages) == 2
316
+ assert "/v1/completions" in logged_messages[0]
317
+ assert "/v1/chat/completions" in logged_messages[1]
318
+
319
+ def test_filter_allows_non_uvicorn_access_logs(self):
320
+ """Test filter allows logs from non-uvicorn.access loggers."""
321
+ filter = UvicornAccessLogFilter(excluded_paths=["/health"])
322
+
323
+ # Log record from a different logger name
324
+ record = logging.LogRecord(
325
+ name="uvicorn.error",
326
+ level=logging.INFO,
327
+ pathname="",
328
+ lineno=0,
329
+ msg="Some error message about /health",
330
+ args=(),
331
+ exc_info=None,
332
+ )
333
+
334
+ # Should allow because it's not from uvicorn.access
335
+ assert filter.filter(record) is True
336
+
337
+ def test_filter_handles_malformed_args(self):
338
+ """Test filter handles log records with unexpected args format."""
339
+ filter = UvicornAccessLogFilter(excluded_paths=["/health"])
340
+
341
+ # Log record with insufficient args
342
+ record = logging.LogRecord(
343
+ name="uvicorn.access",
344
+ level=logging.INFO,
345
+ pathname="",
346
+ lineno=0,
347
+ msg="Some message",
348
+ args=("only", "two"),
349
+ exc_info=None,
350
+ )
351
+
352
+ # Should allow because args doesn't have expected format
353
+ assert filter.filter(record) is True
354
+
355
+ def test_filter_handles_non_tuple_args(self):
356
+ """Test filter handles log records with non-tuple args."""
357
+ filter = UvicornAccessLogFilter(excluded_paths=["/health"])
358
+
359
+ # Log record with None args
360
+ record = logging.LogRecord(
361
+ name="uvicorn.access",
362
+ level=logging.INFO,
363
+ pathname="",
364
+ lineno=0,
365
+ msg="Some message without args",
366
+ args=None,
367
+ exc_info=None,
368
+ )
369
+
370
+ # Should allow because args is None
371
+ assert filter.filter(record) is True
vllm/entrypoints/openai/api_server.py
+35
-2
@@ -264,6 +264,39 @@ def load_log_config(log_config_file: str | None) -> dict | None:
264
return None
265
266
267
+def get_uvicorn_log_config(args: Namespace) -> dict | None:
268
+ """
269
+ Get the uvicorn log config based on the provided arguments.
270
+
271
+ Priority:
272
+ 1. If log_config_file is specified, use it
273
+ 2. If disable_access_log_for_endpoints is specified, create a config with
274
+ the access log filter
275
+ 3. Otherwise, return None (use uvicorn defaults)
276
+ """
277
+ # First, try to load from file if specified
278
+ log_config = load_log_config(args.log_config_file)
279
+ if log_config is not None:
280
+ return log_config
281
+
282
+ # If endpoints to filter are specified, create a config with the filter
283
+ if args.disable_access_log_for_endpoints:
284
+ from vllm.logging_utils import create_uvicorn_log_config
285
+
286
+ # Parse comma-separated string into list
287
+ excluded_paths = [
288
+ p.strip()
289
+ for p in args.disable_access_log_for_endpoints.split(",")
290
+ if p.strip()
291
+ ]
292
+ return create_uvicorn_log_config(
293
+ excluded_paths=excluded_paths,
294
+ log_level=args.uvicorn_log_level,
295
+ )
296
+
297
+ return None
298
+
299
+
300
class AuthenticationMiddleware:
301
"""
302
Pure ASGI middleware that authenticates each request by checking
@@ -930,8 +963,8 @@ async def run_server_worker(
963
if args.reasoning_parser_plugin and len(args.reasoning_parser_plugin) > 3:
964
ReasoningParserManager.import_reasoning_parser(args.reasoning_parser_plugin)
965
933
- # Load logging config for uvicorn if specified
934
- log_config = load_log_config(args.log_config_file)
966
+ # Get uvicorn log config (from file or with endpoint filter)
967
+ log_config = get_uvicorn_log_config(args)
968
if log_config is not None:
969
uvicorn_kwargs["log_config"] = log_config
970
vllm/entrypoints/openai/cli_args.py
+11
@@ -85,6 +85,12 @@ class FrontendArgs:
85
"""Log level for uvicorn."""
86
disable_uvicorn_access_log: bool = False
87
"""Disable uvicorn access log."""
88
+ disable_access_log_for_endpoints: str | None = None
89
+ """Comma-separated list of endpoint paths to exclude from uvicorn access
90
+ logs. This is useful to reduce log noise from high-frequency endpoints
91
+ like health checks. Example: "/health,/metrics,/ping".
92
+ When set, access logs for requests to these paths will be suppressed
93
+ while keeping logs for other endpoints."""
94
allow_credentials: bool = False
95
"""Allow credentials."""
96
allowed_origins: list[str] = field(default_factory=lambda: ["*"])
@@ -244,6 +250,11 @@ class FrontendArgs:
250
del frontend_kwargs["middleware"]["nargs"]
251
frontend_kwargs["middleware"]["default"] = []
252
253
+ # Special case: disable_access_log_for_endpoints is a single
254
+ # comma-separated string, not a list
255
+ if "nargs" in frontend_kwargs["disable_access_log_for_endpoints"]:
256
+ del frontend_kwargs["disable_access_log_for_endpoints"]["nargs"]
257
+
258
# Special case: Tool call parser shows built-in options.
259
valid_tool_parsers = list(ToolParserManager.list_registered())
260
parsers_str = ",".join(valid_tool_parsers)
vllm/logging_utils/__init__.py
+6
@@ -1,6 +1,10 @@
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
+)
8
from vllm.logging_utils.formatter import ColoredFormatter, NewLineFormatter
9
from vllm.logging_utils.lazy import lazy
10
from vllm.logging_utils.log_time import logtime
@@ -8,6 +12,8 @@ from vllm.logging_utils.log_time import logtime
12
__all__ = [
13
"NewLineFormatter",
14
"ColoredFormatter",
15
+ "UvicornAccessLogFilter",
16
+ "create_uvicorn_log_config",
17
"lazy",
18
"logtime",
19
]
vllm/logging_utils/access_log_filter.py
new
+144
@@ -0,0 +1,144 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""
4
+Access log filter for uvicorn to exclude specific endpoints from logging.
5
+
6
+This module provides a logging filter that can be used to suppress access logs
7
+for specific endpoints (e.g., /health, /metrics) to reduce log noise in
8
+production environments.
9
+"""
10
+
11
+import logging
12
+from urllib.parse import urlparse
13
+
14
+
15
+class UvicornAccessLogFilter(logging.Filter):
16
+ """
17
+ A logging filter that excludes access logs for specified endpoint paths.
18
+
19
+ This filter is designed to work with uvicorn's access logger. It checks
20
+ the log record's arguments for the request path and filters out records
21
+ matching the excluded paths.
22
+
23
+ Uvicorn access log format:
24
+ '%s - "%s %s HTTP/%s" %d'
25
+ (client_addr, method, path, http_version, status_code)
26
+
27
+ Example:
28
+ 127.0.0.1:12345 - "GET /health HTTP/1.1" 200
29
+
30
+ Args:
31
+ excluded_paths: A list of URL paths to exclude from logging.
32
+ Paths are matched exactly.
33
+ Example: ["/health", "/metrics"]
34
+ """
35
+
36
+ def __init__(self, excluded_paths: list[str] | None = None):
37
+ super().__init__()
38
+ self.excluded_paths = set(excluded_paths or [])
39
+
40
+ def filter(self, record: logging.LogRecord) -> bool:
41
+ """
42
+ Determine if the log record should be logged.
43
+
44
+ Args:
45
+ record: The log record to evaluate.
46
+
47
+ Returns:
48
+ True if the record should be logged, False otherwise.
49
+ """
50
+ if not self.excluded_paths:
51
+ return True
52
+
53
+ # This filter is specific to uvicorn's access logs.
54
+ if record.name != "uvicorn.access":
55
+ return True
56
+
57
+ # The path is the 3rd argument in the log record's args tuple.
58
+ # See uvicorn's access logging implementation for details.
59
+ log_args = record.args
60
+ if isinstance(log_args, tuple) and len(log_args) >= 3:
61
+ path_with_query = log_args[2]
62
+ # Get path component without query string.
63
+ if isinstance(path_with_query, str):
64
+ path = urlparse(path_with_query).path
65
+ if path in self.excluded_paths:
66
+ return False
67
+
68
+ return True
69
+
70
+
71
+def create_uvicorn_log_config(
72
+ excluded_paths: list[str] | None = None,
73
+ log_level: str = "info",
74
+) -> dict:
75
+ """
76
+ Create a uvicorn logging configuration with access log filtering.
77
+
78
+ This function generates a logging configuration dictionary that can be
79
+ passed to uvicorn's `log_config` parameter. It sets up the access log
80
+ filter to exclude specified paths.
81
+
82
+ Args:
83
+ excluded_paths: List of URL paths to exclude from access logs.
84
+ log_level: The log level for uvicorn loggers.
85
+
86
+ Returns:
87
+ A dictionary containing the logging configuration.
88
+
89
+ Example:
90
+ >>> config = create_uvicorn_log_config(["/health", "/metrics"])
91
+ >>> uvicorn.run(app, log_config=config)
92
+ """
93
+ config = {
94
+ "version": 1,
95
+ "disable_existing_loggers": False,
96
+ "filters": {
97
+ "access_log_filter": {
98
+ "()": UvicornAccessLogFilter,
99
+ "excluded_paths": excluded_paths or [],
100
+ },
101
+ },
102
+ "formatters": {
103
+ "default": {
104
+ "()": "uvicorn.logging.DefaultFormatter",
105
+ "fmt": "%(levelprefix)s %(message)s",
106
+ "use_colors": None,
107
+ },
108
+ "access": {
109
+ "()": "uvicorn.logging.AccessFormatter",
110
+ "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
111
+ },
112
+ },
113
+ "handlers": {
114
+ "default": {
115
+ "formatter": "default",
116
+ "class": "logging.StreamHandler",
117
+ "stream": "ext://sys.stderr",
118
+ },
119
+ "access": {
120
+ "formatter": "access",
121
+ "class": "logging.StreamHandler",
122
+ "stream": "ext://sys.stdout",
123
+ "filters": ["access_log_filter"],
124
+ },
125
+ },
126
+ "loggers": {
127
+ "uvicorn": {
128
+ "handlers": ["default"],
129
+ "level": log_level.upper(),
130
+ "propagate": False,
131
+ },
132
+ "uvicorn.error": {
133
+ "level": log_level.upper(),
134
+ "handlers": ["default"],
135
+ "propagate": False,
136
+ },
137
+ "uvicorn.access": {
138
+ "handlers": ["access"],
139
+ "level": log_level.upper(),
140
+ "propagate": False,
141
+ },
142
+ },
143
+ }
144
+ return config