背景
記錄日誌,在任何專案中,都是很重要的。在Flask
專案中,即有Flask
提供的logger
可以用來記錄log
,也可以通過直接使用Python
的logging
模組自定義logger
來記錄。那麼這兩者是什麼關係,又該怎麼使用呢?
思路
-
Python
的logging
模組先看下對於
logging
模組的官方介紹Loggers have the following attributes and methods. Note that Loggers are never instantiated directly, but always through the module-level function
logging.getLogger(name)
. Multiple calls togetLogger()
with the same name will always return a reference to the same Logger object.The
name
is potentially a period-separated hierarchical value, likefoo.bar.baz
(though it could also be just plainfoo
, for example). Loggers that are further down in the hierarchical list are children of loggers higher up in the list. For example, given a logger with a name offoo
, loggers with names offoo.bar
,foo.bar.baz
, andfoo.bam
are all descendants offoo
. The logger name hierarchy is analogous to the Python package hierarchy, and identical to it if you organise your loggers on a per-module basis using the recommended constructionlogging.getLogger(__name__)
. That’s because in a module,__name__
is the module’s name in the Python package namespace.上面主要告訴我們三點,
- 可以通過
logging.getLogger(name)
來獲取一個logger
,相同名字的logger
,其實是同一個logger
。 -
logger
是通過name
進行繼承的,比如foo.bar
就是foo
的子logger
。就可以是實現我們通過配置一個rootLogger
,然後直接使用rootLogger.sublogger
來記錄一下內容,而不需要單獨再配置一遍。 - 當使用
logging.getLogger(__name__)
時,__name__
就是這個模組所在的python package
的namespace
。
- 可以通過
-
flask提供的logger
再看下flask中的logging模組:
Flask uses standard Python
logging
. All Flask-related messages are logged under the`flask`
logger namespace.Flask.logger
returns the logger named`flask.app`
, and can be used to log messages for your application.Depending on the situation, an extension may choose to log to
app.logger
or its own named logger. Consult each extension’s documentation for details.我們可以知道flask的logger就是一個標準的Python logging,它的命名是
flask
。我們既可以使用app.logger
,也可以自己定義一個logger
。那麼如何使用
app.logger
呢?有兩種方式:
-
直接呼叫
logger = logging.getLogger(`flask.app`) logger.info(`flask.app`)
-
使用
Flask
提供的介面from flask import current_app current_app.logger.info(`logged by current_app from main`)
-
這裡推薦還是使用第二種,current_app
是一個單例,可以直接引用到app.logger
。
-
通過修改
app.logger
的name
,可以實現子logger
的繼承麼?答案是否定的。
-
修改
app.logger
的name
:# app/__init__.py app.logger.name = `app`
然後在子模組中定義一個
app.module
的logger
來記錄:from flask import current_app import logging logger = logging.getLogger(`app.module`) @module.route(`/test`, methods=[`GET`]) def test(): logger.info(`logged by app.module`) current_app.logger.info(`logged by current_app.logger`)
輸出結果:
2019-02-01 10:56:01,877 - Thread-2 - app - INFO - logged by current_app.logger
只有
current_app.logger
的輸出。 -
修改
app.logger
的name
是不是無效呢?我們把子模組中的
logger
的name
修改為flask.app.module
:from flask import current_app import logging logger = logging.getLogger(`flask.app.module`) @module.route(`/test`, methods=[`GET`]) def test(): logger.info(`logged by flask.app.module`) current_app.logger.info(`logged by current_app.logger`)
輸出結果:
2019-02-01 11:00:10,944 - Thread-2 - flask.app.module - INFO - logged by flask.app.module 2019-02-01 11:00:10,946 - Thread-2 - app - INFO - logged by current_app.logger
兩個
logger
均輸出了。
-
可見,通過修改app.logger.name
可以在記錄的時候顯示為我們設定的名稱,但實際上這個logger
還是flask.app
。
-
__name__
的使用在自定義
logger
的情況下,為了方便起見,我們可以利用__name__
這個引數。前面說到:當使用
logging.getLogger(__name__)
時,__name__
就是這個模組所在的python package
的namespace
。一般
Flask
的工廠模式結構如下:app ├── __init__.py ├── main │ ├── __init__.py │ ├── functions.py │ └── views.py └── module ├── __init__.py ├── functions.py └── views.py
那麼我們在先在
app.__init__
中定義rootLogger
,然後再在app.module.functions.py
中定義子Logger
,均使用logging.getLogger(__name__)
:# app.__init__.py 初始化rootlogger rootLogger = logging.getLogger(__name__) rootLogger.setLevel(logging.DEBUG) socketHandler = logging.handlers.SocketHandler(`localhost`,logging.handlers.DEFAULT_TCP_LOGGING_PORT) rootLogger.addHandler(socketHandler) rootLogger.setLevel(logging.DEBUG) # app.module.functions.py import logging logger = logging.getLogger(__name__) def record_from_logging(): logger.info(`logged by logging from __name__`)
輸出:
2019-02-01 12:18:34,743 - MainThread - app - INFO - register root logger by __name__ 2019-02-01 12:19:24,954 - Thread-4 - app.module.functions - INFO - logged by logging from __name__
可以發現輸出的
logger.name
就是所在的檔案目錄,logger
之間的繼承關係與整個程式包保持一致。
總結
根據上面分析,那麼怎麼優雅的記錄logger
呢?
-
如果沒有對模組進行分
logger
記錄要求的話。可以直接使用在程式初始化的時候配置app.logger
(可以自行設定logger.name
)。在模組中通過import current_app
來記錄:# app.__init__.py def register_logging(app): app.logger.name = `app` # logstash_handler stashHandler = logstash.LogstashHandler(`app.config.get(`ELK_HOST`)`, `app.config.get(`ELK_PORT`)`) app.logger.addHandler(stashHandler) # socket_handler socketHandler = logging.handlers.SocketHandler(`localhost`, logging.handlers.DEFAULT_TCP_LOGGING_PORT) app.logger.addHandler(socketHandler) # app.module.function.py from flask import current_app @module.route(`/test`, methods=[`GET`]) def test(): current_app.logger.info(`logging someting`) return `logged by current_app.logger`
輸出效果:
2019-02-01 13:49:28,998 - Thread-2 - app - INFO - logged by current_app from main 2019-02-01 13:49:38,346 - Thread-3 - app - INFO - logged by current_app of functions
__注意__: 對於
current_app.logger
的引用不能通過如下方式,會有RuntimeError
的報錯。from flask import current_app logger = current_app.logger ## 異常 raise RuntimeError(_app_ctx_err_msg) RuntimeError: Working outside of application context. This typically means that you attempted to use functionality that needed to interface with the current application object in some way. To solve this, set up an application context with app.app_context(). See the documentation for more information.
-
如果希望按自己的實際需求,對模組進行分
logger
記錄要求的話。那麼建議自己設定logger
。# app.__init__.py def register_logging(): # set own root logger rootLogger = logging.getLogger(__name__) rootLogger.setLevel(logging.DEBUG) # socketHandler socketHandler = logging.handlers.SocketHandler(`localhost`,logging.handlers.DEFAULT_TCP_LOGGING_PORT) rootLogger.addHandler(socketHandler) # logstash_handler stashHandler = logstash.LogstashHandler(`app.config.get(`ELK_HOST`)`, `app.config.get(`ELK_PORT`)`) rootLogger.addHandler(stashHandler) rootLogger.setLevel(logging.DEBUG) # app.module.function.py import logging logger = logging.getLogger(__name__) @module.route(`/test`, methods=[`GET`]) def test(): logger.info(`logging someting`) return `logged by logging module`
輸出效果:
2019-02-01 13:49:49,297 - Thread-5 - app.module.views - INFO - logged by flask.app.module 2019-02-01 13:50:01,013 - Thread-7 - app.module.functions - INFO - logged by logging module of functions
完整程式碼可參考:https://github.com/keejo125/flask_logging_demo
注意
關於python
中logging
的配置可參考官網:
在配置handler
時,經常會希望日誌可以按時間分割(TimedRotatingFileHandler)或者按大小分割(RotatingFileHandler).
但是在flask
專案中,尤其開啟多執行緒之後,在分割日誌(doRollover()
)時會有檔案讀寫的異常:
WindowsError: [Error 32]
建議使用SocketHandler,將日誌傳送給單獨的LogServer
來進行二次處理。
簡易的接收socketlog
的LogServer
可參考:https://github.com/keejo125/f…
或者現在流行的stashHandler,將日誌傳送給ELK來進行二次處理。