1+ import json
2+ import logging
13import os
24import sys
35import tempfile
6+ from json .decoder import JSONDecodeError
7+ from tempfile import NamedTemporaryFile
8+ from typing import Any
9+ from unittest .mock import patch
10+ from uuid import uuid4
411
5- from vllm .logger import enable_trace_function_call
12+ import pytest
13+
14+ from vllm .logger import (_DATE_FORMAT , _FORMAT , _configure_vllm_root_logger ,
15+ enable_trace_function_call , init_logger )
16+ from vllm .logging import NewLineFormatter
617
718
819def f1 (x ):
@@ -25,3 +36,179 @@ def test_trace_function_call():
2536 assert "f2" in content
2637 sys .settrace (None )
2738 os .remove (path )
39+
40+
41+ def test_default_vllm_root_logger_configuration ():
42+ """This test presumes that VLLM_CONFIGURE_LOGGING (default: True) and
43+ VLLM_LOGGING_CONFIG_PATH (default: None) are not configured and default
44+ behavior is activated."""
45+ logger = logging .getLogger ("vllm" )
46+ assert logger .level == logging .DEBUG
47+ assert not logger .propagate
48+
49+ handler = logger .handlers [0 ]
50+ assert handler .stream == sys .stdout
51+ assert handler .level == logging .INFO
52+
53+ formatter = handler .formatter
54+ assert formatter is not None
55+ assert isinstance (formatter , NewLineFormatter )
56+ assert formatter ._fmt == _FORMAT
57+ assert formatter .datefmt == _DATE_FORMAT
58+
59+
60+ @patch ("vllm.logger.VLLM_CONFIGURE_LOGGING" , 1 )
61+ @patch ("vllm.logger.VLLM_LOGGING_CONFIG_PATH" , None )
62+ def test_descendent_loggers_depend_on_and_propagate_logs_to_root_logger ():
63+ """This test presumes that VLLM_CONFIGURE_LOGGING (default: True) and
64+ VLLM_LOGGING_CONFIG_PATH (default: None) are not configured and default
65+ behavior is activated."""
66+ root_logger = logging .getLogger ("vllm" )
67+ root_handler = root_logger .handlers [0 ]
68+
69+ unique_name = f"vllm.{ uuid4 ()} "
70+ logger = init_logger (unique_name )
71+ assert logger .name == unique_name
72+ assert logger .level == logging .NOTSET
73+ assert not logger .handlers
74+ assert logger .propagate
75+
76+ message = "Hello, world!"
77+ with patch .object (root_handler , "emit" ) as root_handle_mock :
78+ logger .info (message )
79+
80+ root_handle_mock .assert_called_once ()
81+ _ , call_args , _ = root_handle_mock .mock_calls [0 ]
82+ log_record = call_args [0 ]
83+ assert unique_name == log_record .name
84+ assert message == log_record .msg
85+ assert message == log_record .msg
86+ assert log_record .levelno == logging .INFO
87+
88+
89+ @patch ("vllm.logger.VLLM_CONFIGURE_LOGGING" , 0 )
90+ @patch ("vllm.logger.VLLM_LOGGING_CONFIG_PATH" , None )
91+ def test_logger_configuring_can_be_disabled ():
92+ """This test calls _configure_vllm_root_logger again to test custom logging
93+ config behavior, however mocks are used to ensure no changes in behavior or
94+ configuration occur."""
95+
96+ with patch ("logging.config.dictConfig" ) as dict_config_mock :
97+ _configure_vllm_root_logger ()
98+ dict_config_mock .assert_not_called ()
99+
100+
101+ @patch ("vllm.logger.VLLM_CONFIGURE_LOGGING" , 1 )
102+ @patch (
103+ "vllm.logger.VLLM_LOGGING_CONFIG_PATH" ,
104+ "/if/there/is/a/file/here/then/you/did/this/to/yourself.json" ,
105+ )
106+ def test_an_error_is_raised_when_custom_logging_config_file_does_not_exist ():
107+ """This test calls _configure_vllm_root_logger again to test custom logging
108+ config behavior, however it fails before any change in behavior or
109+ configuration occurs."""
110+ with pytest .raises (RuntimeError ) as ex_info :
111+ _configure_vllm_root_logger ()
112+ assert ex_info .type == RuntimeError
113+ assert "File does not exist" in str (ex_info )
114+
115+
116+ @patch ("vllm.logger.VLLM_CONFIGURE_LOGGING" , 1 )
117+ def test_an_error_is_raised_when_custom_logging_config_is_invalid_json ():
118+ """This test calls _configure_vllm_root_logger again to test custom logging
119+ config behavior, however it fails before any change in behavior or
120+ configuration occurs."""
121+ with NamedTemporaryFile (encoding = "utf-8" , mode = "w" ) as logging_config_file :
122+ logging_config_file .write ("---\n loggers: []\n version: 1" )
123+ logging_config_file .flush ()
124+ with patch ("vllm.logger.VLLM_LOGGING_CONFIG_PATH" ,
125+ logging_config_file .name ):
126+ with pytest .raises (JSONDecodeError ) as ex_info :
127+ _configure_vllm_root_logger ()
128+ assert ex_info .type == JSONDecodeError
129+ assert "Expecting value" in str (ex_info )
130+
131+
132+ @patch ("vllm.logger.VLLM_CONFIGURE_LOGGING" , 1 )
133+ @pytest .mark .parametrize ("unexpected_config" , (
134+ "Invalid string" ,
135+ [{
136+ "version" : 1 ,
137+ "loggers" : []
138+ }],
139+ 0 ,
140+ ))
141+ def test_an_error_is_raised_when_custom_logging_config_is_unexpected_json (
142+ unexpected_config : Any ):
143+ """This test calls _configure_vllm_root_logger again to test custom logging
144+ config behavior, however it fails before any change in behavior or
145+ configuration occurs."""
146+ with NamedTemporaryFile (encoding = "utf-8" , mode = "w" ) as logging_config_file :
147+ logging_config_file .write (json .dumps (unexpected_config ))
148+ logging_config_file .flush ()
149+ with patch ("vllm.logger.VLLM_LOGGING_CONFIG_PATH" ,
150+ logging_config_file .name ):
151+ with pytest .raises (ValueError ) as ex_info :
152+ _configure_vllm_root_logger ()
153+ assert ex_info .type == ValueError
154+ assert "Invalid logging config. Expected Dict, got" in str (ex_info )
155+
156+
157+ @patch ("vllm.logger.VLLM_CONFIGURE_LOGGING" , 1 )
158+ def test_custom_logging_config_is_parsed_and_used_when_provided ():
159+ """This test calls _configure_vllm_root_logger again to test custom logging
160+ config behavior, however mocks are used to ensure no changes in behavior or
161+ configuration occur."""
162+ valid_logging_config = {
163+ "loggers" : {
164+ "vllm.test_logger.logger" : {
165+ "handlers" : [],
166+ "propagate" : False ,
167+ }
168+ },
169+ "version" : 1
170+ }
171+ with NamedTemporaryFile (encoding = "utf-8" , mode = "w" ) as logging_config_file :
172+ logging_config_file .write (json .dumps (valid_logging_config ))
173+ logging_config_file .flush ()
174+ with patch ("vllm.logger.VLLM_LOGGING_CONFIG_PATH" ,
175+ logging_config_file .name ), patch (
176+ "logging.config.dictConfig" ) as dict_config_mock :
177+ _configure_vllm_root_logger ()
178+ assert dict_config_mock .called_with (valid_logging_config )
179+
180+
181+ @patch ("vllm.logger.VLLM_CONFIGURE_LOGGING" , 0 )
182+ def test_custom_logging_config_causes_an_error_if_configure_logging_is_off ():
183+ """This test calls _configure_vllm_root_logger again to test custom logging
184+ config behavior, however mocks are used to ensure no changes in behavior or
185+ configuration occur."""
186+ valid_logging_config = {
187+ "loggers" : {
188+ "vllm.test_logger.logger" : {
189+ "handlers" : [],
190+ }
191+ },
192+ "version" : 1
193+ }
194+ with NamedTemporaryFile (encoding = "utf-8" , mode = "w" ) as logging_config_file :
195+ logging_config_file .write (json .dumps (valid_logging_config ))
196+ logging_config_file .flush ()
197+ with patch ("vllm.logger.VLLM_LOGGING_CONFIG_PATH" ,
198+ logging_config_file .name ):
199+ with pytest .raises (RuntimeError ) as ex_info :
200+ _configure_vllm_root_logger ()
201+ assert ex_info .type is RuntimeError
202+ expected_message_snippet = (
203+ "VLLM_CONFIGURE_LOGGING evaluated to false, but "
204+ "VLLM_LOGGING_CONFIG_PATH was given." )
205+ assert expected_message_snippet in str (ex_info )
206+
207+ # Remember! The root logger is assumed to have been configured as
208+ # though VLLM_CONFIGURE_LOGGING=1 and VLLM_LOGGING_CONFIG_PATH=None.
209+ root_logger = logging .getLogger ("vllm" )
210+ other_logger_name = f"vllm.test_logger.{ uuid4 ()} "
211+ other_logger = init_logger (other_logger_name )
212+ assert other_logger .handlers != root_logger .handlers
213+ assert other_logger .level != root_logger .level
214+ assert other_logger .propagate
0 commit comments