如何優雅的在flask中記錄log

背景

記錄日誌,在任何項目中,都是很重要的。在Flask項目中,即有Flask提供的logger能夠用來記錄log,也能夠經過直接使用Pythonlogging模塊自定義logger來記錄。那麼這二者是什麼關係,又該怎麼使用呢?html

思路

  • Pythonlogging模塊python

    先看下對於logging模塊的官方介紹git

    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 to getLogger() with the same name will always return a reference to the same Logger object.

    The name is potentially a period-separated hierarchical value, like foo.bar.baz (though it could also be just plain foo, 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 of foo, loggers with names of foo.bar, foo.bar.baz, and foo.bam are all descendants of foo. 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 construction logging.getLogger(__name__). That’s because in a module, __name__ is the module’s name in the Python package namespace.github

    https://docs.python.org/3/lib...shell

    上面主要告訴咱們三點,flask

    • 能夠經過logging.getLogger(name)來獲取一個logger,相同名字的logger,實際上是同一個logger
    • logger是經過name進行繼承的,好比foo.bar就是foo 的子logger。就能夠是實現咱們經過配置一個rootLogger,而後直接使用rootLogger.sublogger來記錄一下內容,而不須要單獨再配置一遍。
    • 當使用logging.getLogger(__name__)時,__name__就是這個模塊所在的python packagenamespace
  • flask提供的loggerapi

    再看下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.app

    http://flask.pocoo.org/docs/1...socket

    咱們能夠知道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.loggername,能夠實現子logger的繼承麼?

    答案是否認的。

    • 修改app.loggername

      # app/__init__.py
      app.logger.name = 'app'

      而後在子模塊中定義一個app.modulelogger來記錄:

      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.loggername是否是無效呢?

      咱們把子模塊中的loggername修改成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 packagenamespace

    通常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

注意

關於pythonlogging的配置可參考官網:

https://docs.python.org/3/lib...

在配置handler時,常常會但願日誌能夠按時間分割(TimedRotatingFileHandler)或者按大小分割(RotatingFileHandler).

可是在flask項目中,尤爲開啓多線程以後,在分割日誌(doRollover())時會有文件讀寫的異常:

WindowsError: [Error 32]

建議使用SocketHandler,將日誌發送給單獨的LogServer來進行二次處理。

簡易的接收socketlogLogServer可參考:https://github.com/keejo125/f...

或者如今流行的stashHandler,將日誌發送給ELK來進行二次處理。

相關文章
相關標籤/搜索