"docs/source/community/sponsors.md" did not exist on "f12c3b5b3d076a67662b76d215fd875fd6cdf6d7"
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
42
43
DEFAULT_LOGGING_CONFIG = {
    "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
107
    # 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()
    # default "process" scope: always log
    return True
108
109
110
111
112
113


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

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

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

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


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


159
def _configure_vllm_root_logger() -> None:
160
    logging_config = dict[str, dict[str, Any] | Any]()
161

Nick Hill's avatar
Nick Hill committed
162
    if not envs.VLLM_CONFIGURE_LOGGING and envs.VLLM_LOGGING_CONFIG_PATH:
163
164
165
166
        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 "
167
168
            "VLLM_CONFIGURE_LOGGING or unset VLLM_LOGGING_CONFIG_PATH."
        )
Woosuk Kwon's avatar
Woosuk Kwon committed
169

Nick Hill's avatar
Nick Hill committed
170
    if envs.VLLM_CONFIGURE_LOGGING:
171
        logging_config = DEFAULT_LOGGING_CONFIG
Woosuk Kwon's avatar
Woosuk Kwon committed
172

Nick Hill's avatar
Nick Hill committed
173
174
175
176
177
178
        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"

179
180
181
        vllm_loggers = logging_config["loggers"]["vllm"]
        vllm_loggers["level"] = envs.VLLM_LOGGING_LEVEL

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

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

198
199
200
201
202
    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"

203
204
    if logging_config:
        dictConfig(logging_config)
Woosuk Kwon's avatar
Woosuk Kwon committed
205
206


207
def init_logger(name: str) -> _VllmLogger:
208
209
210
    """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
211

212
213
    logger = logging.getLogger(name)

214
    for method_name, method in _METHODS_TO_PATCH.items():
215
216
217
        setattr(logger, method_name, MethodType(method, logger))

    return cast(_VllmLogger, logger)
Woosuk Kwon's avatar
Woosuk Kwon committed
218
219


220
221
222
223
224
225
226
227
@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)


228
229
230
231
232
233
234
235
236
237
238
239
def current_formatter_type(lgr: Logger) -> Literal["color", "newline", None]:
    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


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

245
246
247
248
249
# 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)

250
251
252
253
logger = init_logger(__name__)


def _trace_calls(log_path, root_dir, frame, event, arg=None):
254
    if event in ["call", "return"]:
255
256
257
258
259
260
261
262
263
        # 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:
264
265
266
267
268
269
270
271
272
273
            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 = ""
274
            with open(log_path, "a") as f:
275
                ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
276
277
278
279
280
281
282
                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"
                    )
283
                else:
284
285
286
287
288
289
                    f.write(
                        f"{ts} Return from"
                        f" {func_name} in {filename}:{lineno}"
                        f" to {last_func_name} in {last_filename}:"
                        f"{last_lineno}\n"
                    )
290
291
292
293
294
295
        except NameError:
            # modules are deleted during shutdown
            pass
    return partial(_trace_calls, log_path, root_dir)


296
def enable_trace_function_call(log_file_path: str, root_dir: str | None = None):
297
298
299
300
301
302
303
304
305
306
307
308
309
    """
    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 "
310
311
        "is suggested to be used for debugging hang or crashes only."
    )
312
    logger.info("Trace frame log is saved to %s", log_file_path)
313
314
315
316
    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))