解决uvicorn两种启动方式,打印日志不一致的问题会不会loghandler不一致?为了测试这个想法,添加了调试代码。defget_logger_info(msg:str):LOGGERS=(logging.getLogger(name)fornameinlogging.root.manager.loggerDictifname.startswith("uvicorn"))foruvicorn_loggerinLOGGERS:print(msg,uvicorn_logger.name,uvicorn_logger.handlers,len(uvicorn_logger.handlers))在main函数中,uvicorn配置前后,添加get_logger_info调试代码if__name__=="__main__":......get_logger("Beforeinituvicorn:")config=uvicorn.Config("main:app",host=SERVEICE_HOST_IP,port=int(SERVICE_HOST_PORT),access_log=True,workers=1)server=uvicorn.Server(config)get_logger("Afterinituvicorn:")server.run()启动pythonmain.py,结果如下:2022-07-2014:38:46.376|主线程|信息|main::224-日志级别:DEBUG2022-07-2014:38:46.380|主线程|信息|main::225-数据存储目录:E:\vitural\envs\videorecord\VideoRecordProxy\dataBeforeinituvicorn:uvicorn.error[]0Beforeinituvicorn:uvicorn[]1在初始化uvicorn之前:uvicorn.access[]1在初始化uvicorn之后:uvicorn.error[]0在初始化uvicorn之后:uvicorn[(NOTSET)>]1Afterinituvicorn:uvicorn.access[(NOTSET)>]1INFO:Startedserverprocess[22648]INFO:Waitingforapplicationstartup.INFO:Applicationstartupcomplete.INFO:Uvicornrunningonhttp://0.0.0.0:8000(PressCTRL+Ctoquit)果然,运行uvicorn.Config()之前指定的拦截handler会在uvicorn.Config()之后被其他的handler代替。不难想象uvicorn.Config()会配置一个默认的日志配置。查看uvicorn源码,确实如此。Config类如果不传入log_config,默认会指定LOGGING_CONFIG。类配置:def__init__(self,app:Union[ASGIApplication,Callable,str],host:str="127.0.0.1",port:int=8000,uds:Optional[str]=None,fd:Optional[int]=无,循环:LoopSetupType=“auto”,http:Union[Type[asyncio.Protocol],HTTPProtocolType]=“auto”,ws:Union[Type[asyncio.Protocol],WSProtocolType]=“auto”,ws_max_size:int=16*1024*1024,ws_ping_interval:可选[float]=20,ws_ping_timeout:可选[float]=20,寿命:LifespanType=“auto”,env_file:可选[Union[str,os.PathLike]]=None,log_config:Optional[Union[dict,str]]=LOGGING_CONFIG,....再来看下LOGGING_CONFIG到底有什么:LOGGING_CONFIG:dict={"version":1,"disable_existing_loggers":False,"formatters":{"default":{"()":"uvicorn.logging.DefaultFormatter","fmt":"%(levelprefix)s%(message)s","use_colors":None,},"access":{"()":"uvicorn.logging.AccessFormatter","fmt":'%(levelprefix)s%(client_addr)s-"%(request_line)s"%(status_code)s',#noqa:E501},},"handlers":{"default":{“格式化程序”:“默认”,“类”:“logging.StreamHandler”,“流”:“ext://sys.stderr”,},“访问”:{“格式化程序”:“访问”,“类”:“logging.StreamHandler”,“流”:“ext://sys.stdout”,},},“记录器”:{“uvicorn”:{“处理程序”:["default"],"level":"INFO"},"uvicorn.error":{"level":"INFO"},"uvicorn.access":{"handlers":["access"],"level":"INFO","propagate":False},},}解决方法:uvicorn代码启动时,初始化日志拦截器的过程在uvicorn.Config()之后执行:if__name__=="__main__":config=uvicorn.Config("main:app",host=SERVEICE_HOST_IP,port=int(SERVICE_HOST_PORT),access_log=True,workers=1)server=uvicorn.Server(config)init_logger()server.run()definit_logger():LOGGER_NAMES=("uvicorn","uvicorn.access",)forlogger_nameinLOGGER_NAMES:logging_logger=logging.getLogger(logger_name)logging_logger.handlers=[InterceptHandler()]logger.configure(**loguru_config)预期打印:2022-07-2015:00:03.137|主线程|信息|服务器:服务:84-启动服务器进程[22244]2022-07-2015:00:03.141|主线程|信息|on:startup:45-等待应用程序启动。2022-07-2015:00:03.147|主线程|信息|on:startup:59-应用程序启动完成。2022-07-2015:00:03.156|主线程|信息|server:_log_started_message:222-Uvicorn在http://0.0.0.0:8000上运行(按CTRL+C退出)2022-07-2015:00:13.635|主线程|信息|httptools_impl:send:447-127.0.0.1:62664-“GET/docsHTTP/1.1”2002022-07-2015:00:25.161|主线程|信息|httptools_impl:send:447-127.0.0.1:62665-"GET/openapi.jsonHTTP/1.1"200注意重复打印问题要分析日志重复打印问题,首先要了解logger的两个特点object:1)logger对象有父子关系。当没有父logger对象时,其父对象为root2)logger对象进行日志记录时,其所有父对象也可以同时接收到日志。这个特性是由propagate属性控制的,官网解释:propagate如果这个属性为真,记录到这个logger的事件除了发送到这个logger的logger的所有handlers之外,还会被传递给更高级别(ancestor)的记录处理程序,以及与此记录器关联的任何处理程序。消息直接传递给祖先记录器的处理程序——无论祖先记录器的级别和过滤器如何。了解了以上特点后,就很容易看出重复打印的问题了。LOGGING_CONFIG中有3个uvicorn相关的logger,"uvicorn"、"uvicorn.error"、"uvicorn.access",还有一个rootlogger,即""。在我的例子中,只选择了“uvicorn”和“uvicorn.access”两个logger来指定handler,uvicorn.access的propagate默认为False,所以不会出现重复日志。但是,如果我还为“uvicorn.error”指定了处理程序:definit_logger():LOGGER_NAMES=("uvicorn","uvicorn.access","uvicorn.error",)forlogger_nameinLOGGER_NAMES:logger_name)logging_logger.handlers=[InterceptHandler()]logger.configure(**loguru_config)LOGGING_CONFIG中uvicorn.error的propagate属性为True,其父对象“uvicorn”也会收到日志,并会重复打印。2022-07-2015:53:32.790|主线程|信息|服务器:服务:84-启动服务器进程[22396]2022-07-2015:53:32.934|主线程|信息|过程[22396]2022-07-2015:53:32.936|主线程|信息|on:startup:45-等待应用程序启动。2022-07-2015:53:32.937|主线程|信息|on:startup:45-等待应用程序启动。2022-07-2015:53:32.943|主线程|信息|on:startup:59-应用程序启动完成。2022-07-2015:53:32.944|主线程|信息|59-应用程序启动完成。2022-07-2015:53:32.948|主线程|信息|server:_log_started_message:222-Uvicorn在http://0.0.0.0:8000上运行(按CTRL+C退出)2022-07-2015:53:32.950|主线程|信息|server:_log_started_message:222-Uvicornrunningonhttp://0.0.0.0:8000(PressCTRL+Ctoquit)因此,要解决重复打印的问题,需要控制父子Logger之间的Log传递。