[CI] Support `FORCE_COLOR` env var; enable colors in CPU CI output (#56913)

Signed-off-by: Nick Hill <nickhill123@gmail.com>

Nick Hill committed Sep 16, 2026 at 19:40 UTC 5efbf0886a7110ae0801b9540e3072e89a23eb36
5 files changed +30 -3
.buildkite/scripts/hardware_ci/run-cpu-test-arm.sh
+1 -1
@@ -30,7 +30,7 @@ remove_docker_container
30 docker pull "$IMAGE"
31
32 # Run the image
33 -docker run -itd --cpuset-cpus="$CORE_RANGE" --entrypoint /bin/bash -v ~/.cache/huggingface:/root/.cache/huggingface -e HF_TOKEN --env VLLM_CPU_KVCACHE_SPACE=16 --env VLLM_CPU_CI_ENV=1 -e E2E_OMP_THREADS="$OMP_CORE_RANGE" --shm-size=4g --name "$CONTAINER_NAME" "$IMAGE"
33 +docker run -itd --cpuset-cpus="$CORE_RANGE" --entrypoint /bin/bash -v ~/.cache/huggingface:/root/.cache/huggingface -e HF_TOKEN --env VLLM_CPU_KVCACHE_SPACE=16 --env VLLM_CPU_CI_ENV=1 -e E2E_OMP_THREADS="$OMP_CORE_RANGE" -e TERM=xterm-256color -e PY_COLORS=1 -e FORCE_COLOR=1 -e CLICOLOR_FORCE=1 --shm-size=4g --name "$CONTAINER_NAME" "$IMAGE"
34
35 print_packages() {
36 docker exec "$CONTAINER_NAME" bash -c "
.buildkite/scripts/hardware_ci/run-cpu-test.sh
+1 -1
@@ -99,7 +99,7 @@ rm -f "$build_log"
99 OFFLINE_RETRY_PATTERN='huggingface_hub\.errors\.(LocalEntryNotFoundError|OfflineModeIsEnabled)|Invalid repository ID or local directory specified|Cannot find any model weights with'
100 run_test() {
101 local hf_offline=$1
102 - docker run --rm --cpuset-cpus="$CORE_RANGE" --cpuset-mems="$NUMA_NODE" -v ~/.cache/huggingface:/root/.cache/huggingface -v ~/.cache/vllm:/root/.cache/vllm --privileged=true -e HF_TOKEN -e VLLM_CPU_KVCACHE_SPACE=16 -e VLLM_CPU_CI_ENV=1 -e VLLM_CPU_SIM_MULTI_NUMA=1 -e VLLM_CPU_ATTN_SPLIT_KV=0 -e HF_HUB_OFFLINE="$hf_offline" -e HF_DATASETS_OFFLINE="$hf_offline" --shm-size=4g "$IMAGE_NAME" \
102 + docker run --rm --cpuset-cpus="$CORE_RANGE" --cpuset-mems="$NUMA_NODE" -v ~/.cache/huggingface:/root/.cache/huggingface -v ~/.cache/vllm:/root/.cache/vllm --privileged=true -e HF_TOKEN -e VLLM_CPU_KVCACHE_SPACE=16 -e VLLM_CPU_CI_ENV=1 -e VLLM_CPU_SIM_MULTI_NUMA=1 -e VLLM_CPU_ATTN_SPLIT_KV=0 -e HF_HUB_OFFLINE="$hf_offline" -e HF_DATASETS_OFFLINE="$hf_offline" -e TERM=xterm-256color -e PY_COLORS=1 -e FORCE_COLOR=1 -e CLICOLOR_FORCE=1 --shm-size=4g "$IMAGE_NAME" \
103 timeout "$TIMEOUT_VAL" bash -c "set -euox pipefail; echo \"--- Print packages\"; pip list; echo \"--- Running tests\"; ${TEST_COMMAND}"
104 }
105
tests/test_logger.py
+23
@@ -1,6 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import enum
4 +import io
5 import json
6 import logging
7 import os
@@ -19,6 +20,7 @@ from vllm.logger import (
20 _DATE_FORMAT,
21 _FORMAT,
22 _configure_vllm_root_logger,
23 + _use_color,
24 enable_trace_function_call,
25 init_logger,
26 )
@@ -72,6 +74,27 @@ def test_default_vllm_root_logger_configuration(monkeypatch):
74 assert formatter.datefmt == _DATE_FORMAT
75
76
77 +def test_use_color_force_color(monkeypatch):
78 + """FORCE_COLOR forces colored logs without a TTY, while NO_COLOR and an
79 + explicit VLLM_LOGGING_COLOR=0 take precedence over it."""
80 + monkeypatch.setattr(sys, "stdout", io.StringIO())
81 + monkeypatch.setattr(sys, "stderr", io.StringIO())
82 + for var in ("NO_COLOR", "FORCE_COLOR", "VLLM_LOGGING_COLOR"):
83 + monkeypatch.delenv(var, raising=False)
84 +
85 + assert not _use_color()
86 +
87 + monkeypatch.setenv("FORCE_COLOR", "1")
88 + assert _use_color()
89 +
90 + monkeypatch.setenv("VLLM_LOGGING_COLOR", "0")
91 + assert not _use_color()
92 + monkeypatch.delenv("VLLM_LOGGING_COLOR")
93 +
94 + monkeypatch.setenv("NO_COLOR", "1")
95 + assert not _use_color()
96 +
97 +
98 def test_descendent_loggers_depend_on_and_propagate_logs_to_root_logger(monkeypatch):
99 """This test presumes that VLLM_CONFIGURE_LOGGING (default: True) and
100 VLLM_LOGGING_CONFIG_PATH (default: None) are not configured and default
vllm/envs.py
+4
@@ -44,6 +44,7 @@ if TYPE_CHECKING:
44 VLLM_LOGGING_CONFIG_PATH: str | None = None
45 VLLM_LOGGING_COLOR: str = "auto"
46 NO_COLOR: bool = False
47 + FORCE_COLOR: bool = False
48 VLLM_LOG_STATS_INTERVAL: float = 10.0
49 VLLM_TRACE_FUNCTION: int = 0
50 VLLM_USE_FLASHINFER_SAMPLER: bool = True
@@ -840,6 +841,8 @@ environment_variables: dict[str, Callable[[], Any]] = {
841 "VLLM_LOGGING_COLOR": lambda: os.getenv("VLLM_LOGGING_COLOR", "auto"),
842 # Standard unix flag for disabling ANSI color codes
843 "NO_COLOR": lambda: os.getenv("NO_COLOR", "0") != "0",
844 + # De-facto standard flag for forcing ANSI color codes (e.g. non-tty case)
845 + "FORCE_COLOR": lambda: os.getenv("FORCE_COLOR", "0") != "0",
846 # If set, vllm will log stats at this interval in seconds
847 # If not set, vllm will log stats every 10 seconds.
848 "VLLM_LOG_STATS_INTERVAL": lambda: (
@@ -2335,6 +2338,7 @@ def compile_factors() -> dict[str, object]:
2338 "CUDA_VISIBLE_DEVICES",
2339 "NO_COLOR",
2340 "VLLM_SKIP_VERSION_SUFFIX",
2341 + "FORCE_COLOR",
2342 }
2343
2344 from vllm.config.utils import normalize_value
vllm/logger.py
+1 -1
@@ -29,7 +29,7 @@ _DATE_FORMAT = "%m-%d %H:%M:%S"
29 def _use_color() -> bool:
30 if envs.NO_COLOR or envs.VLLM_LOGGING_COLOR == "0":
31 return False
32 - if envs.VLLM_LOGGING_COLOR == "1":
32 + if envs.VLLM_LOGGING_COLOR == "1" or envs.FORCE_COLOR:
33 return True
34 if envs.VLLM_LOGGING_STREAM == "ext://sys.stdout": # stdout
35 return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()