资讯专栏INFORMATION COLUMN

如何优雅的在flask中记录log

hsluoyz / 2763人阅读

摘要:一般的工厂模式结构如下那么我们在先在中定义,然后再在中定义子,均使用初始化输出可以发现输出的就是所在的文件目录,之间的继承关系与整个程序包保持一致。在模块中通过来记录输出效果注意对于的引用不能通过如下方式,会有的报错。

背景

记录日志,在任何项目中,都是很重要的。在Flask项目中,即有Flask提供的logger可以用来记录log,也可以通过直接使用Pythonlogging模块自定义logger来记录。那么这两者是什么关系,又该怎么使用呢?

思路

Pythonlogging模块

先看下对于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 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.

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

上面主要告诉我们三点,

可以通过logging.getLogger(name)来获取一个logger,相同名字的logger,其实是同一个logger

logger是通过name进行继承的,比如foo.bar就是foo 的子logger。就可以是实现我们通过配置一个rootLogger,然后直接使用rootLogger.sublogger来记录一下内容,而不需要多带带再配置一遍。

当使用logging.getLogger(__name__)时,__name__就是这个模块所在的python packagenamespace

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.

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

我们可以知道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来进行二次处理。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/43142.html

相关文章

  • flask文档学习笔记1-快速入门

    摘要:示例如下静态路由使用动态变量的路由未指定变量类型使用动态变量的路由指定变量类型指定的路由变量,可以作为被装饰的函数参数传入进来。 开始决定认真的在网上写一些东西,主要原因还是在于希望能提升学习效果。虽说python写了有几年,但是web后端框架的确没怎么接触过,买了本狗书寥寥草草的过了一遍,发现很多东西还是理解不深,真的是好记性不如烂笔头,知识也要从基础开始,退回来好好看看官方文档,再...

    lingdududu 评论0 收藏0
  • Sanic + 前端MVVM 一种新一代Python高性能全栈开发实践

    摘要:前端一种新一代高性能全栈开发实践背景本项目将使用配合最简单的逻辑来展示一个基于的全新一代高性能全栈开发实践的为什么是对于为何不是等著名框架,或许可能很多人会产生疑惑,本身和非常的相似,而它的出现,不仅是大大改进过去时代性能低下通病,外加配 SanicCRUD-vue Sanic + 前端MVVM 一种新一代Python高性能全栈开发实践showImg(https://segmentfa...

    Profeel 评论0 收藏0
  • Sanic + 前端MVVM 一种新一代Python高性能全栈开发实践

    摘要:前端一种新一代高性能全栈开发实践背景本项目将使用配合最简单的逻辑来展示一个基于的全新一代高性能全栈开发实践的为什么是对于为何不是等著名框架,或许可能很多人会产生疑惑,本身和非常的相似,而它的出现,不仅是大大改进过去时代性能低下通病,外加配 SanicCRUD-vue Sanic + 前端MVVM 一种新一代Python高性能全栈开发实践showImg(https://segmentfa...

    NusterCache 评论0 收藏0
  • mdwiki开发之路二资源与踩坑记录

    摘要:侧边栏选用提到的。将改成来访问的形式组织代码出现循环的问题往往意味着代码的布局有问题,可以合并或者分离竞争资源。分离的话就是把需要的资源提取到一个第三方文件去。总之就是将循环变成单向。对于周期性任务缺一不可。其他任务可仅运行。 1、bootstrap代码片段: 如果你没有艺术细胞,偷懒的方法就是到这上面去找,比如登录框界面等。侧边栏选用:http://www.designerslib....

    molyzzx 评论0 收藏0
  • mdwiki开发之路二资源与踩坑记录

    摘要:侧边栏选用提到的。将改成来访问的形式组织代码出现循环的问题往往意味着代码的布局有问题,可以合并或者分离竞争资源。分离的话就是把需要的资源提取到一个第三方文件去。总之就是将循环变成单向。对于周期性任务缺一不可。其他任务可仅运行。 1、bootstrap代码片段: 如果你没有艺术细胞,偷懒的方法就是到这上面去找,比如登录框界面等。侧边栏选用:http://www.designerslib....

    linkFly 评论0 收藏0

发表评论

0条评论

hsluoyz

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<