"vllm/vscode:/vscode.git/clone" did not exist on "fd5fd2690275e90865023a0bcac0047ecb3f3897"
logger.py 10.8 KB
Newer Older
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
"""Logging configuration for vLLM."""
4

5
import datetime
6
import json
Woosuk Kwon's avatar
Woosuk Kwon committed
7
import logging
8
import os
9
import sys
10
11
from collections.abc import Generator, Hashable
from contextlib import contextmanager
12
from functools import lru_cache, partial
13
14
15
from logging import Logger
from logging.config import dictConfig
from os import path
16
from types import MethodType
17
from typing import Any, Literal, cast
Woosuk Kwon's avatar
Woosuk Kwon committed
18

19
import vllm.envs as envs
20
from vllm.logging_utils import ColoredFormatter, NewLineFormatter
21

22
_FORMAT = (
Nick Hill's avatar
Nick Hill committed
23
    f"{envs.VLLM_LOGGING_PREFIX}%(levelname)s %(asctime)s "
24
25
    "[%(fileinfo)s:%(lineno)d] %(message)s"
)
Woosuk Kwon's avatar
Woosuk Kwon committed
26
27
_DATE_FORMAT = "%m-%d %H:%M:%S"

Nick Hill's avatar
Nick Hill committed
28
29
30
31
32
33
34
35
36
37
38
39
40

def _use_color() -> bool:
    if envs.NO_COLOR or envs.VLLM_LOGGING_COLOR == "0":
        return False
    if envs.VLLM_LOGGING_COLOR == "1":
        return True
    if envs.VLLM_LOGGING_STREAM == "ext://sys.stdout":  # stdout
        return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
    elif envs.VLLM_LOGGING_STREAM == "ext://sys.stderr":  # stderr
        return hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
    return False


41
DEFAULT_LOGGING_CONFIG: dict[str, dict[str, Any] | Any] = {
42
43
    "formatters": {
        "vllm": {
44
            "class": "vllm.logging_utils.NewLineFormatter",
45
46
47
            "datefmt": _DATE_FORMAT,
            "format": _FORMAT,
        },
Nick Hill's avatar
Nick Hill committed
48
49
50
51
52
        "vllm_color": {
            "class": "vllm.logging_utils.ColoredFormatter",
            "datefmt": _DATE_FORMAT,
            "format": _FORMAT,
        },
53
54
55
56
    },
    "handlers": {
        "vllm": {
            "class": "logging.StreamHandler",
Nick Hill's avatar
Nick Hill committed
57
58
59
60
            # Choose formatter based on color setting.
            "formatter": "vllm_color" if _use_color() else "vllm",
            "level": envs.VLLM_LOGGING_LEVEL,
            "stream": envs.VLLM_LOGGING_STREAM,
61
62
63
64
65
        },
    },
    "loggers": {
        "vllm": {
            "handlers": ["vllm"],
66
            "level": envs.VLLM_LOGGING_LEVEL,
67
68
69
70
            "propagate": False,
        },
    },
    "version": 1,
71
    "disable_existing_loggers": False,
72
73
74
}


75
76
@lru_cache
def _print_debug_once(logger: Logger, msg: str, *args: Hashable) -> None:
77
78
    # Set the stacklevel to 3 to print the original caller's line info
    logger.debug(msg, *args, stacklevel=3)
79
80


81
@lru_cache
82
def _print_info_once(logger: Logger, msg: str, *args: Hashable) -> None:
83
84
    # Set the stacklevel to 3 to print the original caller's line info
    logger.info(msg, *args, stacklevel=3)
85
86
87


@lru_cache
88
def _print_warning_once(logger: Logger, msg: str, *args: Hashable) -> None:
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
    # Set the stacklevel to 3 to print the original caller's line info
    logger.warning(msg, *args, stacklevel=3)


LogScope = Literal["process", "global", "local"]


def _should_log_with_scope(scope: LogScope) -> bool:
    """Decide whether to log based on scope"""
    if scope == "global":
        from vllm.distributed.parallel_state import is_global_first_rank

        return is_global_first_rank()
    if scope == "local":
        from vllm.distributed.parallel_state import is_local_first_rank

        return is_local_first_rank()
    return True
107
108
109
110
111
112


class _VllmLogger(Logger):
    """
    Note:
        This class is just to provide type information.
113
        We actually patch the methods directly on the [`logging.Logger`][]
114
115
116
117
        instance to avoid conflicting with other libraries such as
        `intel_extension_for_pytorch.utils._logger`.
    """

118
    def debug_once(self, msg: str, *args: Hashable, scope: LogScope = "local") -> None:
119
120
121
122
        """
        As [`debug`][logging.Logger.debug], but subsequent calls with
        the same message are silently dropped.
        """
123
124
        if not _should_log_with_scope(scope):
            return
125
126
        _print_debug_once(self, msg, *args)

127
    def info_once(self, msg: str, *args: Hashable, scope: LogScope = "local") -> None:
128
        """
129
130
        As [`info`][logging.Logger.info], but subsequent calls with
        the same message are silently dropped.
131
        """
132
133
        if not _should_log_with_scope(scope):
            return
134
        _print_info_once(self, msg, *args)
135

136
    def warning_once(
137
        self, msg: str, *args: Hashable, scope: LogScope = "local"
138
    ) -> None:
139
        """
140
141
        As [`warning`][logging.Logger.warning], but subsequent calls with
        the same message are silently dropped.
142
        """
143
144
        if not _should_log_with_scope(scope):
            return
145
        _print_warning_once(self, msg, *args)
146
147


148
149
# Pre-defined methods mapping to avoid repeated dictionary creation
_METHODS_TO_PATCH = {
150
151
152
    "debug_once": _VllmLogger.debug_once,
    "info_once": _VllmLogger.info_once,
    "warning_once": _VllmLogger.warning_once,
153
154
155
}


156
def _configure_vllm_root_logger() -> None:
157
    logging_config: dict[str, dict[str, Any] | Any] = {}
158

Nick Hill's avatar
Nick Hill committed
159
    if not envs.VLLM_CONFIGURE_LOGGING and envs.VLLM_LOGGING_CONFIG_PATH:
160
161
162
163
        raise RuntimeError(
            "VLLM_CONFIGURE_LOGGING evaluated to false, but "
            "VLLM_LOGGING_CONFIG_PATH was given. VLLM_LOGGING_CONFIG_PATH "
            "implies VLLM_CONFIGURE_LOGGING. Please enable "
164
165
            "VLLM_CONFIGURE_LOGGING or unset VLLM_LOGGING_CONFIG_PATH."
        )
Woosuk Kwon's avatar
Woosuk Kwon committed
166

Nick Hill's avatar
Nick Hill committed
167
    if envs.VLLM_CONFIGURE_LOGGING:
168
        logging_config = DEFAULT_LOGGING_CONFIG
Woosuk Kwon's avatar
Woosuk Kwon committed
169

Nick Hill's avatar
Nick Hill committed
170
171
172
173
174
175
        vllm_handler = logging_config["handlers"]["vllm"]
        # Refresh these values in case env vars have changed.
        vllm_handler["level"] = envs.VLLM_LOGGING_LEVEL
        vllm_handler["stream"] = envs.VLLM_LOGGING_STREAM
        vllm_handler["formatter"] = "vllm_color" if _use_color() else "vllm"

176
177
178
        vllm_loggers = logging_config["loggers"]["vllm"]
        vllm_loggers["level"] = envs.VLLM_LOGGING_LEVEL

Nick Hill's avatar
Nick Hill committed
179
180
    if envs.VLLM_LOGGING_CONFIG_PATH:
        if not path.exists(envs.VLLM_LOGGING_CONFIG_PATH):
181
182
            raise RuntimeError(
                "Could not load logging config. File does not exist: %s",
Nick Hill's avatar
Nick Hill committed
183
                envs.VLLM_LOGGING_CONFIG_PATH,
184
            )
Nick Hill's avatar
Nick Hill committed
185
        with open(envs.VLLM_LOGGING_CONFIG_PATH, encoding="utf-8") as file:
186
            custom_config = json.loads(file.read())
Woosuk Kwon's avatar
Woosuk Kwon committed
187

188
        if not isinstance(custom_config, dict):
189
190
191
192
            raise ValueError(
                "Invalid logging config. Expected dict, got %s.",
                type(custom_config).__name__,
            )
193
        logging_config = custom_config
Woosuk Kwon's avatar
Woosuk Kwon committed
194

195
196
197
198
199
    for formatter in logging_config.get("formatters", {}).values():
        # This provides backwards compatibility after #10134.
        if formatter.get("class") == "vllm.logging.NewLineFormatter":
            formatter["class"] = "vllm.logging_utils.NewLineFormatter"

200
201
    if logging_config:
        dictConfig(logging_config)
Woosuk Kwon's avatar
Woosuk Kwon committed
202
203


204
def init_logger(name: str) -> _VllmLogger:
205
206
207
    """The main purpose of this function is to ensure that loggers are
    retrieved in such a way that we can be sure the root vllm logger has
    already been configured."""
Woosuk Kwon's avatar
Woosuk Kwon committed
208

209
210
    logger = logging.getLogger(name)

211
    for method_name, method in _METHODS_TO_PATCH.items():
212
213
214
        setattr(logger, method_name, MethodType(method, logger))

    return cast(_VllmLogger, logger)
Woosuk Kwon's avatar
Woosuk Kwon committed
215
216


217
218
219
220
221
222
223
224
@contextmanager
def suppress_logging(level: int = logging.INFO) -> Generator[None, Any, None]:
    current_level = logging.root.manager.disable
    logging.disable(level)
    yield
    logging.disable(current_level)


225
226
def current_formatter_type(logger: Logger) -> Literal["color", "newline", None]:
    lgr: Logger | None = logger
227
228
229
230
231
232
233
234
235
236
237
    while lgr is not None:
        if lgr.handlers and len(lgr.handlers) == 1 and lgr.handlers[0].name == "vllm":
            formatter = lgr.handlers[0].formatter
            if isinstance(formatter, ColoredFormatter):
                return "color"
            if isinstance(formatter, NewLineFormatter):
                return "newline"
        lgr = lgr.parent
    return None


238
# The root logger is initialized when the module is imported.
Woosuk Kwon's avatar
Woosuk Kwon committed
239
240
# This is thread-safe as the module is only imported once,
# guaranteed by the Python GIL.
241
_configure_vllm_root_logger()
242

243
244
245
246
247
# Transformers uses httpx to access the Hugging Face Hub. httpx is quite verbose,
# so we set its logging level to WARNING when vLLM's logging level is INFO.
if envs.VLLM_LOGGING_LEVEL == "INFO":
    logging.getLogger("httpx").setLevel(logging.WARNING)

248
249
250
251
logger = init_logger(__name__)


def _trace_calls(log_path, root_dir, frame, event, arg=None):
252
    if event in ["call", "return"]:
253
254
255
256
257
258
259
260
261
        # Extract the filename, line number, function name, and the code object
        filename = frame.f_code.co_filename
        lineno = frame.f_lineno
        func_name = frame.f_code.co_name
        if not filename.startswith(root_dir):
            # only log the functions in the vllm root_dir
            return
        # Log every function call or return
        try:
262
263
264
265
266
267
268
269
270
271
            last_frame = frame.f_back
            if last_frame is not None:
                last_filename = last_frame.f_code.co_filename
                last_lineno = last_frame.f_lineno
                last_func_name = last_frame.f_code.co_name
            else:
                # initial frame
                last_filename = ""
                last_lineno = 0
                last_func_name = ""
272
            with open(log_path, "a") as f:
273
                ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
274
275
276
277
278
279
280
                if event == "call":
                    f.write(
                        f"{ts} Call to"
                        f" {func_name} in {filename}:{lineno}"
                        f" from {last_func_name} in {last_filename}:"
                        f"{last_lineno}\n"
                    )
281
                else:
282
283
284
285
286
287
                    f.write(
                        f"{ts} Return from"
                        f" {func_name} in {filename}:{lineno}"
                        f" to {last_func_name} in {last_filename}:"
                        f"{last_lineno}\n"
                    )
288
289
290
291
292
293
        except NameError:
            # modules are deleted during shutdown
            pass
    return partial(_trace_calls, log_path, root_dir)


294
def enable_trace_function_call(log_file_path: str, root_dir: str | None = None):
295
296
297
298
299
300
301
302
303
304
305
306
307
    """
    Enable tracing of every function call in code under `root_dir`.
    This is useful for debugging hangs or crashes.
    `log_file_path` is the path to the log file.
    `root_dir` is the root directory of the code to trace. If None, it is the
    vllm root directory.

    Note that this call is thread-level, any threads calling this function
    will have the trace enabled. Other threads will not be affected.
    """
    logger.warning(
        "VLLM_TRACE_FUNCTION is enabled. It will record every"
        " function executed by Python. This will slow down the code. It "
308
309
        "is suggested to be used for debugging hang or crashes only."
    )
310
    logger.info("Trace frame log is saved to %s", log_file_path)
311
312
313
314
    if root_dir is None:
        # by default, this is the vllm root directory
        root_dir = os.path.dirname(os.path.dirname(__file__))
    sys.settrace(partial(_trace_calls, log_file_path, root_dir))