flask框架實(shí)戰(zhàn)—優(yōu)雅的使用log模塊

前言

本文轉(zhuǎn)載自:https://www.flyml.net
最近在做一個(gè)測(cè)試工具平臺(tái),選擇使用flask框架吨凑。過(guò)程中遇到一些 bug需要定位喘帚,之前一直使用的print調(diào)試。但是感覺(jué)這樣太麻煩反粥,浪費(fèi)了很多時(shí)間卢肃。于是使用log來(lái)進(jìn)行定位。
于是上網(wǎng)查詢了一些文檔才顿。發(fā)現(xiàn)網(wǎng)上很雜亂莫湘,初學(xué)者看了很可能一臉懵逼。沒(méi)有一篇很完整的使用說(shuō)明郑气。我這里嘗試歸納下幅垮,并總結(jié)自己的用法。如果你有什么疑問(wèn)或者更好的建議尾组,歡迎一起交流學(xué)習(xí)哦~本人QQ:995774387

使用current_app

from flask import current_app 是什么忙芒?

簡(jiǎn)單來(lái)講,current_app表示當(dāng)前運(yùn)行程序文件的程序?qū)嵗淝龋瑢儆趹?yīng)用上下文呵萨。如有興趣深入了解可以查看文章《flask開發(fā)之--請(qǐng)求/應(yīng)用上下文》
所以,在程序?qū)嵗倪^(guò)程中跨跨,設(shè)置好log的路徑潮峦,參數(shù)等信息。在views.py(視圖函數(shù))中勇婴,使用current_app.logger.info("這是條測(cè)試日志")就可以實(shí)現(xiàn)日志打印啦忱嘹!
備注:pycharm等IDE工具有時(shí)并不能自動(dòng)匹配到current_app.logger,放心調(diào)用即可耕渴。

flask--log使用

會(huì)從以下幾個(gè)問(wèn)題切入討論使用:

  • 日志在Flask之中的基礎(chǔ)使用方法
  • 如何在Flask之中配置日志的格式拘悦、文件存儲(chǔ)地址、自動(dòng)切分日志
  • 在Blueprint之中如何使用日志
  • 通過(guò)郵件或者Http接口輸出錯(cuò)誤日志
  • 多機(jī)環(huán)境下萨螺, 如何使用日志進(jìn)行定位的思路(比如需要增加hostname 定位具體在哪個(gè)docker環(huán)境)

日志在Flask之中的基礎(chǔ)使用方法

首先我們從最簡(jiǎn)單的Flask程序開始窄做。 從官網(wǎng)復(fù)制一個(gè)最小的能運(yùn)行的Flask程序愧驱, 如下:

# -*- coding:utf-8 -*-
# Copied From :http://flask.pocoo.org/docs/1.0/quickstart/#a-minimal-application
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.debug = True
    app.run()

毫無(wú)疑問(wèn), 訪問(wèn)http://127.0.0.1:5000 就能看到Hello World的輸出椭盏。 接下來(lái)组砚, 我們開始設(shè)置日志。 具體參考下面的代碼:

# -*- coding:utf-8 -*-
from flask import Flask
import logging
from logging import FileHandler

app = Flask(__name__)

@app.route('/')
def hello_world():
    app.logger.info("Info message")
    app.logger.warning("Warning msg")
    app.logger.error("Error msg!!!")
    return 'Hello, World!'

if __name__ == '__main__':
    app.debug = True
    handler = logging.FileHandler('flask.log')
    app.logger.addHandler(handler)
    app.run()

我們?cè)?code>main 函數(shù)之中掏颊, 設(shè)定:

  • 我們會(huì)將日志寫到flask.log之中
  • Flask自帶的app.logger 使用我們?cè)O(shè)定好的handler
    再次訪問(wèn)http://127.0.0.1:5000 糟红,在Console看到如下信息:
[2018-12-11 20:41:07,344] INFO in main: Info message
[2018-12-11 20:41:07,344] WARNING in main: Warning msg
[2018-12-11 20:41:07,345] ERROR in main: Error msg!!!
127.0.0.1 - - [11/Dec/2018 20:41:07] "GET / HTTP/1.1" 200 -

可以看到, 我們?cè)?code>hello_world 函數(shù)之中期望打印出來(lái)的日志都正常輸出了乌叶。 同時(shí)盆偿, 你也應(yīng)該能看到一個(gè)flask.log文件。 不過(guò)里面的內(nèi)容就不如Console的log那樣准浴, 包含了時(shí)間來(lái)源等等的信息了事扭。 內(nèi)容如下:

Info message
Warning msg
Error msg!!!

日志配置

雖然是在Flask之中調(diào)用了自帶的app.logger, 但是畢竟還是使用了公共庫(kù)的logging乐横。 配置方面應(yīng)該能找到很多很多資料求橄。 再次演示一下自認(rèn)為比較好用的一個(gè)配置。

如果一個(gè)日志不停的增長(zhǎng)下去葡公, 顯然不是什么好事罐农。 因此日志必須要進(jìn)行切分。 常見的兩種方式:

  • 按照大小
  • 按照時(shí)間

按照日志大小切分
如果是按照大小進(jìn)行切分催什, 引入RotatingFileHandler 即可涵亏。 舉例:

from logging.handlers import RotatingFileHandler 
handler = RotatingFileHandler("flask.log", maxBytes=1024000, backupCount=10)

簡(jiǎn)單解釋一下:

  • “flask.log” 就是日志的文件名
  • maxBytes 就是 日志大小
  • backupCount 就是保留的日志個(gè)數(shù)。 比如flask.log 寫滿了蒲凶, 就會(huì)被重命名成flask.log.1, 程序繼續(xù)向flask.log寫入气筋。

更詳細(xì)的解釋可以看看官網(wǎng)說(shuō)明: https://docs.python.org/2/library/logging.handlers.html#rotatingfilehandler
按照日期進(jìn)行切分
個(gè)人比較習(xí)慣這種方式。 在logging這個(gè)庫(kù)之中豹爹, 還支持按照分鐘裆悄、小時(shí)、天等級(jí)別進(jìn)行切分臂聋。 根據(jù)我們業(yè)務(wù)的大小光稼, 我一般選擇按照“天” 進(jìn)行切分。 可以參考下面的配置:

from logging.handlers import TimedRotatingFileHandler
handler = TimedRotatingFileHandler(
        "flask.log", when="D", interval=1, backupCount=15,
        encoding="UTF-8", delay=False, utc=True)
  • when=D: 表示按天進(jìn)行切分
  • interval=1: 每天都切分孩等。 比如interval=2就表示兩天切分一下艾君。
  • backupCount=15: 保留15天的日志
  • encoding=UTF-8: 使用UTF-8的編碼來(lái)寫日志
  • utc=True: 使用UTC+0的時(shí)間來(lái)記錄 (一般docker鏡像默認(rèn)也是UTC+0)

配置日志格式
前面我們也看到, 在日志文件之中肄方, 除了記錄下來(lái)的消息冰垄, 其他輔助信息完全沒(méi)有。 以下是我自己的配置以及相應(yīng)的輸出

# My Config
# [%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s

# Output
[2018-12-11 21:13:23,315][main.py:10][INFO][24012] - Info message
[2018-12-11 21:13:23,315][main.py:11][WARNING][24012] - Warning msg
[2018-12-11 21:13:23,316][main.py:12][ERROR][24012] - Error msg!!!

注意: 設(shè)置%(thread)d 并不是必須的权她。 但是如果你在一個(gè)多線程虹茶、或者多個(gè)docker環(huán)境的時(shí)候逝薪, 加上這個(gè)thread, 有助于你把同一個(gè)會(huì)話進(jìn)程抽取出來(lái)蝴罪。 因?yàn)槎噙M(jìn)程董济、多線程的時(shí)候, 日志的順序可能會(huì)被打亂要门。

Blueprint 之中使用日志

當(dāng)你的Flask項(xiàng)目膨脹到一定規(guī)模的時(shí)候虏肾, 全部都寫到主入口之中。 一定需要按照模塊進(jìn)行拆分欢搜。 Blueprint(藍(lán)圖)就是這個(gè)時(shí)候需要使用的東西封豪。 那么在Blueprint之中, 如何使用日志呢炒瘟?

我們先基于前面的程序搭好框架:
主入口 main.py

# -*- coding:utf-8 -*-
from flask import Flask
import logging
from logging.handlers import TimedRotatingFileHandler

from views.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page, url_prefix="/simple_page")


@app.route('/')
def hello_world():
    app.logger.info("Info message")
    app.logger.warning("Warning msg")
    app.logger.error("Error msg!!!")
    return 'Hello, World!'


if __name__ == '__main__':
    app.debug = True
    formatter = logging.Formatter(
        "[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s")
    handler = TimedRotatingFileHandler(
        "flask.log", when="D", interval=1, backupCount=15,
        encoding="UTF-8", delay=False, utc=True)
    app.logger.addHandler(handler)
    handler.setFormatter(formatter)

    app.run()

注意: 已經(jīng)注冊(cè)好了藍(lán)圖simple_page, 并且設(shè)置了url_prefix=simple_page
藍(lán)圖 views.simple_page

from flask import Blueprint

simple_page = Blueprint('simple_page', __name__)


@simple_page.route('/')
def show():
    return "simple page"

文件目錄長(zhǎng)下面這樣:


flask-blueprint-file-structure.jpg

其實(shí)在blueprint之中吹埠, 使用日志的方式也很簡(jiǎn)單。 參考下面simple_page.py之中增加日志調(diào)用之后的代碼:

from flask import Blueprint
from flask import current_app

simple_page = Blueprint('simple_page', __name__)


@simple_page.route('/')
def show():
    current_app.logger.info("simple page info...")
    current_app.logger.warning("warning msg!")
    current_app.logger.error("ERROR!!!!!")
    return "simple page"

關(guān)鍵就是from flask import current_app就可以獲取當(dāng)前的flask的app了唧领。 我們來(lái)看看在日志文件之中的日志的樣子:

[2018-12-12 08:39:13,431][simple_page.py:9][INFO][22908] - simple page info...
[2018-12-12 08:39:13,433][simple_page.py:10][WARNING][22908] - warning msg!
[2018-12-12 08:39:13,433][simple_page.py:11][ERROR][22908] - ERROR!!!!!

看起來(lái)一切正常藻雌。 Console 之中的日志:

[2018-12-12 08:39:13,431] INFO in simple_page: simple page info...
[2018-12-12 08:39:13,433] WARNING in simple_page: warning msg!
[2018-12-12 08:39:13,433] ERROR in simple_page: ERROR!!!!!

看起來(lái)也不錯(cuò)。 完美斩个!

錯(cuò)誤日志發(fā)送郵件或者調(diào)用HTTP接口

當(dāng)系統(tǒng)上線之后, 多多少少程序會(huì)因?yàn)楦鞣N各樣的問(wèn)題產(chǎn)生Error級(jí)別的日志驯杜。 但是我們又不能一直盯著線上日志受啥。 一個(gè)簡(jiǎn)單的辦法, 當(dāng)出現(xiàn)錯(cuò)誤日志的時(shí)候鸽心, 主動(dòng)通知滚局。 比如發(fā)郵件或者調(diào)用Http Webhook 接口。

在標(biāo)準(zhǔn)日志庫(kù)logging之中顽频,就有SMTPHandlerHTTPHandler可以實(shí)現(xiàn)這個(gè)功能藤肢。 下面以發(fā)郵件為例子。

我們接著上面Blueprint的代碼接著寫糯景。
主入口main.py

# -*- coding:utf-8 -*-
from flask import Flask
import logging
from logging.handlers import TimedRotatingFileHandler
from logging.handlers import SMTPHandler

from views.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page, url_prefix="/simple_page")


@app.route('/')
def hello_world():
    app.logger.info("Info message")
    app.logger.warning("Warning msg")
    app.logger.error("Error msg----1")
    app.logger.error("Error msg----2")
    app.logger.error("Error msg----3")
    return 'Hello, World!'


if __name__ == '__main__':
    app.debug = True

    # File and Console handler & formtter
    formatter = logging.Formatter(
        "[%(asctime)s][%(module)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s")
    handler = TimedRotatingFileHandler(
        "flask.log", when="D", interval=1, backupCount=15,
        encoding="UTF-8", delay=False, utc=True)
    app.logger.addHandler(handler)
    handler.setFormatter(formatter)

    # Email Handler
    mail_handler = SMTPHandler(
        mailhost='10.64.1.85',
        fromaddr='flask-admin@trendmicro.com',
        toaddrs=['wenjun_yang@trendmicro.com'],
        subject='Flask Application Error'
    )
    mail_handler.setLevel(logging.ERROR)
    mail_handler.setFormatter(logging.Formatter(
        "[%(asctime)s][%(module)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s"
    ))
    app.logger.addHandler(mail_handler)

    app.run()

關(guān)鍵部分:

# 引入類庫(kù)
from logging.handlers import SMTPHandler

# 配置handler&formatter
mail_handler = SMTPHandler(
    mailhost='10.64.xxx,yyy',
    fromaddr='flask-admin@abc.com',
    toaddrs=['superman@abc.com'],
    subject='Flask Application Error'
)
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(logging.Formatter(
    "[%(asctime)s][%(module)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s"
))

# app.logger
app.logger.addHandler(mail_handler)  

注意: 我們?cè)诤瘮?shù)之中嘁圈, 連續(xù)輸入了3條Error信息, EmailHandler并不會(huì)幫助我們合并這三條信息蟀淮, 而是會(huì)分別發(fā)送郵件過(guò)來(lái)最住。


image

在Log之中增加其他的輔助信息

增加輔助信息有兩種比較優(yōu)雅的方式:

  • 通過(guò)LogFilter
  • 通過(guò)自定義的Formatter
    假設(shè)我們?cè)贚og之中需要增加當(dāng)前的環(huán)境的hostname,我們新的formatter長(zhǎng)下面這樣
# 原來(lái)的formatter
[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s

# 新的Formatter
(%(hostname)s)[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s

使用LogFilter的方式

# 首先自定義一個(gè)LogFilter
class ContextFilter(logging.Filter):
    '''Enhances log messages with contextual information'''
    def filter(self, record):
        record.hostname = "my-windows-10"
        return True


# 在main函數(shù)之中怠惶, 增加加載這個(gè)filter即可
handler.addFilter(ContextFilter())

多機(jī)環(huán)境下的錯(cuò)誤定位思路

比如我們使用前后端的架構(gòu)涨缚, 會(huì)在多臺(tái)機(jī)器甚至分布在不同數(shù)據(jù)中心的機(jī)器上面部署相同的程序, 相互構(gòu)成一個(gè)集群策治。

這種場(chǎng)景下面脓魏, 出現(xiàn)錯(cuò)誤的時(shí)候兰吟, 其實(shí)定位問(wèn)題是非常麻煩的。 所以茂翔, 首先我們要定位在哪個(gè)環(huán)境上面出的問(wèn)題揽祥。同時(shí), 我們還需要借助其他的手段檩电。 下面說(shuō)一下思路:

增加hostname 字段
flask的日志默認(rèn)是不帶hostname字段的拄丰, 增加的方法就需要用到上一節(jié)提到的方法。

通過(guò)ELK等方式俐末, 將日志統(tǒng)一收集到一個(gè)集中的地方進(jìn)行處理

如果使用ELK料按, 可以直接在Kibana上面查找Error級(jí)別的日志, 并且通過(guò)thread / hostname 等過(guò)濾出某一個(gè)環(huán)境上面的日志進(jìn)行查看卓箫。

  • 增加hostname 字段
  • flask的日志默認(rèn)是不帶hostname字段的载矿, 增加的方法就需要用到上一節(jié)提到的方法。
  • 通過(guò)ELK等方式烹卒, 將日志統(tǒng)一收集到一個(gè)集中的地方進(jìn)行處理
  • 如果使用ELK闷盔, 可以直接在Kibana上面查找Error級(jí)別的日志, 并且通過(guò)thread / hostname 等過(guò)濾出某一個(gè)環(huán)境上面的日志進(jìn)行查看旅急。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逢勾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藐吮,更是在濱河造成了極大的恐慌溺拱,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谣辞,死亡現(xiàn)場(chǎng)離奇詭異迫摔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)泥从,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門句占,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人躯嫉,你說(shuō)我怎么就攤上這事纱烘。” “怎么了和敬?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵凹炸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我昼弟,道長(zhǎng)啤它,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮变骡,結(jié)果婚禮上离赫,老公的妹妹穿的比我還像新娘。我一直安慰自己塌碌,他們只是感情好渊胸,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著台妆,像睡著了一般翎猛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上接剩,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天切厘,我揣著相機(jī)與錄音,去河邊找鬼懊缺。 笑死疫稿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹃两。 我是一名探鬼主播遗座,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼俊扳!你這毒婦竟也來(lái)了途蒋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拣度,失蹤者是張志新(化名)和其女友劉穎碎绎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抗果,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年奸晴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冤馏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寄啼,死狀恐怖逮光,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情墩划,我是刑警寧澤涕刚,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站乙帮,受9級(jí)特大地震影響杜漠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一驾茴、第九天 我趴在偏房一處隱蔽的房頂上張望盼樟。 院中可真熱鬧,春花似錦锈至、人聲如沸晨缴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)击碗。三九已至,卻和暖如春们拙,著一層夾襖步出監(jiān)牢的瞬間稍途,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工睛竣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晰房,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓射沟,卻偏偏與公主長(zhǎng)得像殊者,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子验夯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容