追了源码,做了测试,终于实现python的uvicorn日志自行配置
一、背景
有人提出,message日志不能放我們自己的服務的日志,需要將該日志單獨搞到一個地方。
二、先說結論
本文直接上結論,可能是全CSDN唯一的解釋。哈哈哈哈哈哈哈哈。
在uvicorn啟動的時候,傳入log_config參數
那么,這個參數如何傳呢?我先給個樣例
LOGGING_CONFIG = {"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',},},"handlers": {"default": {"formatter": "default","class": "logging.handlers.TimedRotatingFileHandler","filename": "./log"},"access": {"formatter": "access","class": "logging.handlers.TimedRotatingFileHandler","filename": "./log"},},"loggers": {"": {"handlers": ["default"], "level": "INFO"},"uvicorn.error": {"level": "INFO"},"uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},}, }這樣的配置,會使unicorn 的日志,按照TimedRotatingFileHandler的默認切割方法,將日志寫到我們配置的./log文件中
三、源碼解析
首先,我們先在一個代碼文件中,將服務跑起來,用官網的命令行方式也是一樣,這里不過多介紹
from typing import Optionalimport uvicorn from fastapi import FastAPIapp = FastAPI()@app.get("/") def read_root():return {"Hello": "World"}@app.get("/items/{item_id}") def read_item(item_id: int, q: Optional[str] = None):return {"item_id": item_id, "q": q}if __name__ == "__main__":uvicorn.run(app=app,host="0.0.0.0",port=12345,)運行結果如下:
遇到了問題,先去看了看官方文檔。
但官方文檔,就這么一點點,只說有個參數能配置,十分令人生氣。(迅速調整情緒)開始讀源碼。
點住 run 方法,進去看看
再點開Config進去看看
原來,這里給了一個默認的日志配置,再往下點 LOGGING_CONFIG
就找到了默認的配置:
LOGGING_CONFIG = {"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',},},"handlers": {"default": {"formatter": "default","class": "logging.StreamHandler","stream": "ext://sys.stderr",},"access": {"formatter": "access","class": "logging.StreamHandler","stream": "ext://sys.stdout",},},"loggers": {"": {"handlers": ["default"], "level": "INFO"},"uvicorn.error": {"level": "INFO"},"uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},}, }再繼續往下追,看看這個 配置使如何處理的呢
來到233行,對字典的配置的處理,繼續往下點,來到了關鍵的代碼:這一塊對字典中的內容進行解析。
?
?
?來到了
self.configure_logger(name, loggers[name])——————————————再往里面進入,點一層?來看這個函數,我們就看到了本質,本質上,還是給logger.addHandler方法。
def common_logger_config(self, logger, config, incremental=False):"""Perform configuration which is common to root and non-root loggers."""level = config.get('level', None)if level is not None:logger.setLevel(logging._checkLevel(level))if not incremental:#Remove any existing handlersfor h in logger.handlers[:]:logger.removeHandler(h)handlers = config.get('handlers', None)if handlers:self.add_handlers(logger, handlers)filters = config.get('filters', None)if filters:self.add_filters(logger, filters)def add_handlers(self, logger, handlers):"""Add handlers to a logger from a list of names."""for h in handlers:try:logger.addHandler(self.config['handlers'][h])except Exception as e:raise ValueError('Unable to add handler %r' % h) from e這段代碼追到這里就可以停止了。回頭追解析部分。
for name in sorted(handlers):try:handler = self.configure_handler(handlers[name])?話不多說,直接把函數貼上來了。
def configure_handler(self, config):"""Configure a handler from a dictionary."""config_copy = dict(config) # for restoring in case of errorformatter = config.pop('formatter', None)if formatter:try:formatter = self.config['formatters'][formatter]except Exception as e:raise ValueError('Unable to set formatter ''%r' % formatter) from elevel = config.pop('level', None)filters = config.pop('filters', None)if '()' in config:c = config.pop('()')if not callable(c):c = self.resolve(c)factory = celse:cname = config.pop('class')klass = self.resolve(cname)#Special case for handler which refers to another handlerif issubclass(klass, logging.handlers.MemoryHandler) and\'target' in config:try:th = self.config['handlers'][config['target']]if not isinstance(th, logging.Handler):config.update(config_copy) # restore for deferred cfgraise TypeError('target not configured yet')config['target'] = thexcept Exception as e:raise ValueError('Unable to set target handler ''%r' % config['target']) from eelif issubclass(klass, logging.handlers.SMTPHandler) and\'mailhost' in config:config['mailhost'] = self.as_tuple(config['mailhost'])elif issubclass(klass, logging.handlers.SysLogHandler) and\'address' in config:config['address'] = self.as_tuple(config['address'])factory = klassprops = config.pop('.', None)kwargs = {k: config[k] for k in config if valid_ident(k)}try:result = factory(**kwargs)except TypeError as te:if "'stream'" not in str(te):raise#The argument name changed from strm to stream#Retry with old name.#This is so that code can be used with older Python versions#(e.g. by Django)kwargs['strm'] = kwargs.pop('stream')result = factory(**kwargs)if formatter:result.setFormatter(formatter)if level is not None:result.setLevel(logging._checkLevel(level))if filters:self.add_filters(result, filters)if props:for name, value in props.items():setattr(result, name, value)return result請注意以下兩行
?
再結合resolve方法,發現,這就是個通過類名獲取該類的方法。然后再把參數傳給該類。
我理解,就是JAVA中的反射機制
源碼就看到這里了。
四、實戰解析
1.首先找到我們想要配置的Handler
可以參考我之前的文章
Python日志詳解【兩篇就夠了系列】--第一篇logging_康雨城的博客-CSDN博客
2.修改已有配置為自己的配置
?
?不難發現,class為類名,后面的參數就是類的參數名。
五、啟動驗證
from typing import Optionalimport uvicorn from fastapi import FastAPIapp = FastAPI()@app.get("/") def read_root():return {"Hello": "World"}@app.get("/items/{item_id}") def read_item(item_id: int, q: Optional[str] = None):return {"item_id": item_id, "q": q}LOGGING_CONFIG = {"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',},},"handlers": {"default": {"formatter": "default","class": "logging.handlers.TimedRotatingFileHandler","filename": "./log"},"access": {"formatter": "access","class": "logging.handlers.TimedRotatingFileHandler","filename": "./log"},},"loggers": {"": {"handlers": ["default"], "level": "INFO"},"uvicorn.error": {"level": "INFO"},"uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},}, }if __name__ == "__main__":uvicorn.run(app=app,host="0.0.0.0",port=12345,log_config=LOGGING_CONFIG,)可以發現,日志打到了文件里面
?
總結
以上是生活随笔為你收集整理的追了源码,做了测试,终于实现python的uvicorn日志自行配置的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从书上截取一段TCP三次握手和四次挥手
- 下一篇: 通过追源码解决:xmlrpc.clien