main
py 137 lines 4.95 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import logging
5 from pathlib import Path
6
7 JSON_FORMAT = (
8 "%(asctime)s %(levelname)s %(name)s %(processName)s %(process)d %(message)s"
9 )
10
11
12 class NewLineFormatter(logging.Formatter):
13 """Adds logging prefix to newlines to align multi-line messages."""
14
15 def __init__(self, fmt, datefmt=None, style="%", *, log_level: str | None = None):
16 super().__init__(fmt, datefmt, style)
17
18 self.use_relpath = log_level == "DEBUG"
19 if self.use_relpath:
20 self.root_dir = Path(__file__).resolve().parent.parent.parent
21
22 def format(self, record):
23 def shrink_path(relpath: Path) -> str:
24 """Shortens a file path for logging display:
25 - Removes leading 'vllm' folder if present.
26 - If path starts with 'v1',
27 keeps the first two and last two levels,
28 collapsing the middle as '...'.
29 - Otherwise, keeps the first and last two levels,
30 collapsing the middle as '...'.
31 - If the path is short, returns it as-is.
32 - Examples:
33 vllm/model_executor/layers/quantization/utils/fp8_utils.py ->
34 model_executor/.../quantization/utils/fp8_utils.py
35 vllm/model_executor/layers/quantization/awq.py ->
36 model_executor/layers/quantization/awq.py
37
38 Args:
39 relpath (Path): The relative path to be shortened.
40
41 Returns:
42 str: The shortened path string for display.
43
44 """
45 parts = list(relpath.parts)
46 new_parts = []
47 if parts and parts[0] == "vllm":
48 parts = parts[1:]
49 if parts and parts[0] == "v1":
50 new_parts += parts[:2]
51 parts = parts[2:]
52 elif parts:
53 new_parts += parts[:1]
54 parts = parts[1:]
55 if len(parts) > 2:
56 new_parts += ["..."] + parts[-2:]
57 else:
58 new_parts += parts
59 return "/".join(new_parts)
60
61 if self.use_relpath:
62 abs_path = getattr(record, "pathname", None)
63 if abs_path:
64 try:
65 relpath = Path(abs_path).resolve().relative_to(self.root_dir)
66 except Exception:
67 relpath = Path(record.filename)
68 else:
69 relpath = Path(record.filename)
70 record.fileinfo = shrink_path(relpath)
71 else:
72 record.fileinfo = record.filename
73
74 msg = super().format(record)
75 if record.message != "":
76 parts = msg.split(record.message)
77 msg = msg.replace("\n", "\r\n" + parts[0])
78 return msg
79
80
81 class ColoredFormatter(NewLineFormatter):
82 """Adds ANSI color codes to log levels for terminal output.
83
84 This formatter adds colors by injecting them into the format string for
85 static elements (timestamp, filename, line number) and modifying the
86 levelname attribute for dynamic color selection.
87 """
88
89 # ANSI color codes
90 COLORS = {
91 "DEBUG": "\033[37m", # White
92 "INFO": "\033[32m", # Green
93 "WARNING": "\033[33m", # Yellow
94 "ERROR": "\033[31m", # Red
95 "CRITICAL": "\033[35m", # Magenta
96 }
97 GREY = "\033[90m" # Grey for timestamp and file info
98 PROCESS_PREFIX_COLOR = "\033[0;36m"
99 PROCESS_PREFIX_RESET = "\033[0;0m"
100 RESET = "\033[0m"
101
102 def __init__(self, fmt, datefmt=None, style="%", *, log_level: str | None = None):
103 # Inject grey color codes into format string for timestamp and file info
104 if fmt:
105 # Wrap process prefix with cyan
106 fmt = fmt.replace(
107 "(%(vllm_process_name)s pid=%(process)d)",
108 f"{self.PROCESS_PREFIX_COLOR}"
109 "(%(vllm_process_name)s pid=%(process)d)"
110 f"{self.PROCESS_PREFIX_RESET}",
111 )
112 # Wrap %(asctime)s with grey
113 fmt = fmt.replace("%(asctime)s", f"{self.GREY}%(asctime)s{self.RESET}")
114 # Wrap [%(fileinfo)s:%(lineno)d] with grey
115 fmt = fmt.replace(
116 "[%(fileinfo)s:%(lineno)d]",
117 f"{self.GREY}[%(fileinfo)s:%(lineno)d]{self.RESET}",
118 )
119
120 # Call parent __init__ with potentially modified format string
121 super().__init__(fmt, datefmt, style, log_level=log_level)
122
123 def format(self, record):
124 # Store original levelname to restore later (in case record is reused)
125 orig_levelname = record.levelname
126
127 # Only modify levelname - it needs dynamic color based on severity
128 if (color_code := self.COLORS.get(record.levelname)) is not None:
129 record.levelname = f"{color_code}{record.levelname}{self.RESET}"
130
131 # Call parent format which will handle everything else
132 msg = super().format(record)
133
134 # Restore original levelname
135 record.levelname = orig_levelname
136
137 return msg