Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion fastdeploy/engine/sched/scheduler_metrics_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""

import logging
import sys
import threading
import time
from typing import Iterable
Expand Down Expand Up @@ -47,7 +48,7 @@ def _get_logger(self) -> logging.Logger:
if not getattr(logger, "_fd_scheduler_metrics_configured", False):
logger.setLevel(logging.INFO)
logger.propagate = False
handler = logging.StreamHandler()
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(
"[%(asctime)s] [%(process)d] [%(levelname)s] %(message)s",
"%Y-%m-%d %H:%M:%S",
Expand Down
3 changes: 2 additions & 1 deletion fastdeploy/entrypoints/openai/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import json
import os
import signal
import sys
import threading
import time
import traceback
Expand Down Expand Up @@ -194,7 +195,7 @@ async def lifespan(app: FastAPI):
"%(levelname)-8s %(asctime)s %(process)-5s %(filename)s[line:%(lineno)d] %(message)s"
)

handler = logging.StreamHandler()
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
uvicorn_access.addHandler(handler)
uvicorn_access.propagate = False
Expand Down
13 changes: 10 additions & 3 deletions fastdeploy/entrypoints/openai/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,35 @@
}
},
"handlers": {
# INFO/DEBUG logs go to stdout
"default": {
"class": "colorlog.StreamHandler",
"stream": "ext://sys.stdout",
"formatter": "custom",
},
# ERROR+ logs go to stderr
"error": {
"class": "colorlog.StreamHandler",
"stream": "ext://sys.stderr",
"level": "ERROR",
"formatter": "custom",
},
},
"loggers": {
"uvicorn": {
"level": "INFO",
"handlers": ["default"],
"handlers": ["default", "error"],
"propagate": False,
},
"uvicorn.error": {
"level": "INFO",
"handlers": ["default"],
"handlers": ["default", "error"],
"propagate": False,
Comment on lines +55 to 78
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前 dictConfig 下同时把 "default"(stdout) 和 "error"(stderr) 两个 handler 绑定到 uvicorn / uvicorn.error,但 "default" handler 没有过滤 ERROR 级别,导致 ERROR+ 日志会同时输出到 stdout 和 stderr(重复一份),与注释“ERROR+ logs go to stderr”不一致。建议给 stdout handler 增加“仅低于 ERROR”的 filter(或等价的过滤方案),确保 ERROR+ 只走 stderr。

Copilot uses AI. Check for mistakes.
},
"uvicorn.access": {
"level": "INFO",
"handlers": ["default"],
"propagate": False,
"formatter": "custom",
},
},
}
Expand Down
8 changes: 2 additions & 6 deletions fastdeploy/logger/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,8 @@ def get_trace_logger(self, name, file_name, without_formater=False, print_to_con
if not without_formater:
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
console_handler.propagate = False

# Set propagate (maintain original logic)
# logger.propagate = False
logger.propagate = False

return logger

Expand Down Expand Up @@ -309,10 +307,8 @@ def _get_legacy_logger(self, name, file_name, without_formater=False, print_to_c
if not without_formater:
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
console_handler.propagate = False

# Set propagate (maintain original logic)
# logger.propagate = False
logger.propagate = False

return logger

Expand Down
6 changes: 6 additions & 0 deletions fastdeploy/logger/setup_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ def setup_logging(log_dir=None, config_file=None):
# Ensure log directory exists
Path(log_dir).mkdir(parents=True, exist_ok=True)

# Prevent implicit basicConfig() from adding a stderr handler when
# module-level logging.info/warning() is called with no root handlers.
# A NullHandler on root satisfies Python's "has handlers" check.
if not logging.root.handlers:
logging.root.addHandler(logging.NullHandler())

Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里给 root logger 在“无 handler”时添加 NullHandler,会抑制 Python 的 lastResort 输出,并且会让后续用户/上层应用调用 logging.basicConfig() 变得无效(因为 root 已经有 handler 了)。这属于对全局 logging 的侵入式修改,可能导致第三方库/未配置 logger 的告警和错误静默丢失。建议避免直接改 root(例如:修复触发 root 的 module-level logging.* 调用、或只在 fastdeploy 自己的 logger 树上加 handler / filter,或提供开关并在文档中说明行为变化)。

Suggested change
# Prevent implicit basicConfig() from adding a stderr handler when
# module-level logging.info/warning() is called with no root handlers.
# A NullHandler on root satisfies Python's "has handlers" check.
if not logging.root.handlers:
logging.root.addHandler(logging.NullHandler())

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR 描述中写的是仅恢复 legacy logger 的 propagate=False 并更新测试,但实际还包含:修改 setup_logging(root NullHandler)、调整 OpenAI 入口的 uvicorn log_config/输出流、以及 scheduler_metrics_logger 输出流等。建议补充 PR 描述说明这些额外改动的动机与影响范围,或拆分为独立 PR,避免 reviewer/发布时遗漏风险评估。

Copilot uses AI. Check for mistakes.
# Store log_dir for later use
setup_logging._log_dir = log_dir

Expand Down
5 changes: 4 additions & 1 deletion tests/logger/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ def test_legacy_logger_creation(self):
def test_logger_propagate(self):
"""Test log propagation settings"""
legacy_logger = self.logger._get_legacy_logger("test", "test.log")
self.assertTrue(legacy_logger.propagate)
self.assertFalse(legacy_logger.propagate)
Comment thread
gongweibao marked this conversation as resolved.
# Also verify get_trace_logger
trace_logger = self.logger.get_trace_logger("test_trace", "test_trace.log")
self.assertFalse(trace_logger.propagate)

def test_get_trace_logger_basic(self):
"""Test basic functionality of get_trace_logger"""
Expand Down
Loading