logger.py 7.4 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
import datetime
5
import json
Woosuk Kwon's avatar
Woosuk Kwon committed
6
import logging
7
import os
8
import sys
9
from collections.abc import Hashable
10
from functools import lru_cache, partial
11
12
13
from logging import Logger
from logging.config import dictConfig
from os import path
14
15
from types import MethodType
from typing import Any, Optional, cast
Woosuk Kwon's avatar
Woosuk Kwon committed
16

17
18
19
20
import vllm.envs as envs

VLLM_CONFIGURE_LOGGING = envs.VLLM_CONFIGURE_LOGGING
VLLM_LOGGING_CONFIG_PATH = envs.VLLM_LOGGING_CONFIG_PATH
21
VLLM_LOGGING_LEVEL = envs.VLLM_LOGGING_LEVEL
22
VLLM_LOGGING_PREFIX = envs.VLLM_LOGGING_PREFIX
23

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

28
29
30
DEFAULT_LOGGING_CONFIG = {
    "formatters": {
        "vllm": {
31
            "class": "vllm.logging_utils.NewLineFormatter",
32
33
34
35
36
37
38
39
            "datefmt": _DATE_FORMAT,
            "format": _FORMAT,
        },
    },
    "handlers": {
        "vllm": {
            "class": "logging.StreamHandler",
            "formatter": "vllm",
40
            "level": VLLM_LOGGING_LEVEL,
41
42
43
44
45
46
47
48
49
50
51
            "stream": "ext://sys.stdout",
        },
    },
    "loggers": {
        "vllm": {
            "handlers": ["vllm"],
            "level": "DEBUG",
            "propagate": False,
        },
    },
    "version": 1,
52
    "disable_existing_loggers": False
53
54
55
}


56
@lru_cache
57
def _print_info_once(logger: Logger, msg: str, *args: Hashable) -> None:
58
    # Set the stacklevel to 2 to print the original caller's line info
59
    logger.info(msg, *args, stacklevel=2)
60
61
62


@lru_cache
63
def _print_warning_once(logger: Logger, msg: str, *args: Hashable) -> None:
64
    # Set the stacklevel to 2 to print the original caller's line info
65
    logger.warning(msg, *args, stacklevel=2)
66
67
68
69
70
71


class _VllmLogger(Logger):
    """
    Note:
        This class is just to provide type information.
72
        We actually patch the methods directly on the [`logging.Logger`][]
73
74
75
76
        instance to avoid conflicting with other libraries such as
        `intel_extension_for_pytorch.utils._logger`.
    """

77
    def info_once(self, msg: str, *args: Hashable) -> None:
78
        """
79
80
        As [`info`][logging.Logger.info], but subsequent calls with
        the same message are silently dropped.
81
        """
82
        _print_info_once(self, msg, *args)
83

84
    def warning_once(self, msg: str, *args: Hashable) -> None:
85
        """
86
87
        As [`warning`][logging.Logger.warning], but subsequent calls with
        the same message are silently dropped.
88
        """
89
        _print_warning_once(self, msg, *args)
90
91


92
def _configure_vllm_root_logger() -> None:
93
    logging_config = dict[str, Any]()
94
95
96
97
98
99
100

    if not VLLM_CONFIGURE_LOGGING and VLLM_LOGGING_CONFIG_PATH:
        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 "
            "VLLM_CONFIGURE_LOGGING or unset VLLM_LOGGING_CONFIG_PATH.")
Woosuk Kwon's avatar
Woosuk Kwon committed
101

102
103
    if VLLM_CONFIGURE_LOGGING:
        logging_config = DEFAULT_LOGGING_CONFIG
Woosuk Kwon's avatar
Woosuk Kwon committed
104

105
106
107
108
109
    if VLLM_LOGGING_CONFIG_PATH:
        if not path.exists(VLLM_LOGGING_CONFIG_PATH):
            raise RuntimeError(
                "Could not load logging config. File does not exist: %s",
                VLLM_LOGGING_CONFIG_PATH)
110
        with open(VLLM_LOGGING_CONFIG_PATH, encoding="utf-8") as file:
111
            custom_config = json.loads(file.read())
Woosuk Kwon's avatar
Woosuk Kwon committed
112

113
        if not isinstance(custom_config, dict):
114
            raise ValueError("Invalid logging config. Expected dict, got %s.",
115
116
                             type(custom_config).__name__)
        logging_config = custom_config
Woosuk Kwon's avatar
Woosuk Kwon committed
117

118
119
120
121
122
    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"

123
124
    if logging_config:
        dictConfig(logging_config)
Woosuk Kwon's avatar
Woosuk Kwon committed
125
126


127
def init_logger(name: str) -> _VllmLogger:
128
129
130
    """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
131

132
133
134
135
136
137
138
139
140
141
142
    logger = logging.getLogger(name)

    methods_to_patch = {
        "info_once": _print_info_once,
        "warning_once": _print_warning_once,
    }

    for method_name, method in methods_to_patch.items():
        setattr(logger, method_name, MethodType(method, logger))

    return cast(_VllmLogger, logger)
Woosuk Kwon's avatar
Woosuk Kwon committed
143
144


145
# The root logger is initialized when the module is imported.
Woosuk Kwon's avatar
Woosuk Kwon committed
146
147
# This is thread-safe as the module is only imported once,
# guaranteed by the Python GIL.
148
_configure_vllm_root_logger()
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

logger = init_logger(__name__)


def _trace_calls(log_path, root_dir, frame, event, arg=None):
    if event in ['call', 'return']:
        # 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:
164
165
166
167
168
169
170
171
172
173
            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 = ""
174
            with open(log_path, 'a') as f:
175
                ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
176
                if event == 'call':
177
                    f.write(f"{ts} Call to"
178
179
180
                            f" {func_name} in {filename}:{lineno}"
                            f" from {last_func_name} in {last_filename}:"
                            f"{last_lineno}\n")
181
                else:
182
                    f.write(f"{ts} Return from"
183
184
185
                            f" {func_name} in {filename}:{lineno}"
                            f" to {last_func_name} in {last_filename}:"
                            f"{last_lineno}\n")
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
        except NameError:
            # modules are deleted during shutdown
            pass
    return partial(_trace_calls, log_path, root_dir)


def enable_trace_function_call(log_file_path: str,
                               root_dir: Optional[str] = None):
    """
    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 "
        "is suggested to be used for debugging hang or crashes only.")
208
    logger.info("Trace frame log is saved to %s", log_file_path)
209
210
211
212
    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))