追了源码,做了测试,终于实现python的uvicorn日志自行配置
一、背景
有人提出,message日志不能放我們自己的服務(wù)的日志,需要將該日志單獨(dú)搞到一個(gè)地方。
二、先說結(jié)論
本文直接上結(jié)論,可能是全CSDN唯一的解釋。哈哈哈哈哈哈哈哈。
在uvicorn啟動(dòng)的時(shí)候,傳入log_config參數(shù)
那么,這個(gè)參數(shù)如何傳呢?我先給個(gè)樣例
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},}, }這樣的配置,會(huì)使unicorn 的日志,按照TimedRotatingFileHandler的默認(rèn)切割方法,將日志寫到我們配置的./log文件中
三、源碼解析
首先,我們先在一個(gè)代碼文件中,將服務(wù)跑起來,用官網(wǎng)的命令行方式也是一樣,這里不過多介紹
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,)運(yùn)行結(jié)果如下:
遇到了問題,先去看了看官方文檔。
但官方文檔,就這么一點(diǎn)點(diǎn),只說有個(gè)參數(shù)能配置,十分令人生氣。(迅速調(diào)整情緒)開始讀源碼。
點(diǎn)住 run 方法,進(jìn)去看看
再點(diǎn)開Config進(jìn)去看看
原來,這里給了一個(gè)默認(rèn)的日志配置,再往下點(diǎn) LOGGING_CONFIG
就找到了默認(rèn)的配置:
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},}, }再繼續(xù)往下追,看看這個(gè) 配置使如何處理的呢
來到233行,對字典的配置的處理,繼續(xù)往下點(diǎn),來到了關(guān)鍵的代碼:這一塊對字典中的內(nèi)容進(jìn)行解析。
?
?
?來到了
self.configure_logger(name, loggers[name])——————————————再往里面進(jìn)入,點(diǎn)一層?來看這個(gè)函數(shù),我們就看到了本質(zhì),本質(zhì)上,還是給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])?話不多說,直接把函數(shù)貼上來了。
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請注意以下兩行
?
再結(jié)合resolve方法,發(fā)現(xiàn),這就是個(gè)通過類名獲取該類的方法。然后再把參數(shù)傳給該類。
我理解,就是JAVA中的反射機(jī)制
源碼就看到這里了。
四、實(shí)戰(zhàn)解析
1.首先找到我們想要配置的Handler
可以參考我之前的文章
Python日志詳解【兩篇就夠了系列】--第一篇logging_康雨城的博客-CSDN博客
2.修改已有配置為自己的配置
?
?不難發(fā)現(xiàn),class為類名,后面的參數(shù)就是類的參數(shù)名。
五、啟動(dòng)驗(yàn)證
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,)可以發(fā)現(xiàn),日志打到了文件里面
?
總結(jié)
以上是生活随笔為你收集整理的追了源码,做了测试,终于实现python的uvicorn日志自行配置的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从书上截取一段TCP三次握手和四次挥手
- 下一篇: python mkl freebsd_f