从 Flask 到 Gin —— Logging
文章目录
本文是 从 Flask 到 Gin 系列的第 2 篇。
在 MJP 项目中,我使用的是 Python 标准库中的 logging 模块。在 Flask 项目启动的时候,创建一个全局的 logger 对象,对其进行基本的设置。
在下面的代码中,_set_logger
被 create_app
调用,初始化整个 MJP 系统的全局 logger 对象。这个 logger 对象与 Flask 中的 app.logger
是等同的。
Flask 中的全局 logger 实现
1# package mjp.app
2
3import logging
4from mjp import config
5
6# 就是 flas.app.logger https://flask.palletsprojects.com/en/1.1.x/logging/,放在这里不必引用 current_app
7logger = logging.getLogger(__name__)
8
9def _set_logger(mjpapp):
10 """
11 设置 Flask app 的logger
12 """
13 # logger = mjpapp.logger
14 # 删除 Flask 的默认 Handler
15 del logger.handlers[:]
16 if mjpapp.config.get('DEBUG'):
17 hdr = logging.StreamHandler()
18 hdr.setLevel(logging.DEBUG)
19 logger.setLevel(logging.DEBUG)
20 else:
21 # logsdir 是一个 Path 实例
22 logsdir = config.getdir('logs')
23 # 创建或者设置 logs 文件夹的权限,让其他 user 也可以写入(例如nginx)
24 # 注意,要设置 777 权限,需要使用 0o40777 或者先设置 os.umask(0)
25 # 0o40777 是根据 os.stat() 获取到的 st_mode 得到的
26 if logsdir.exists():
27 logsdir.chmod(0o40777)
28 else:
29 logsdir.mkdir(mode=0o40777)
30 applog = logsdir.joinpath('app.log')
31 if not applog.exists():
32 applog.touch()
33 # python 3.6 的 FileHandler 才支持 Path 实例。因此这里要做处理
34 hdr = logging.FileHandler(str(applog.resolve()), encoding='utf8')
35 hdr.setLevel(logging.INFO)
36 logger.setLevel(logging.INFO)
37
38 LOG_FORMAT = """
39[%(asctime)s] %(levelname)s in %(module)s.%(funcName)s [%(pathname)s:%(lineno)d]:
40%(message)s"""
41 hdr.setFormatter(logging.Formatter(LOG_FORMAT))
42
43 for log in (logger, logging.getLogger('sqlalchemy')):
44 log.addHandler(hdr)
45
46def create_app(FlaskClass=MJPFlask, ResponseClass=MJPResponse, ConfigClass=FlaskConfig):
47 """
48 根据不同的配置创建 app
49 """
50 mjpapp = FlaskClass(__name__, static_url_path=config.getcfg('PATH', 'STATIC_URL_PATH'))
51 mjpapp.response_class = ResponseClass
52 mjpapp.config.from_object(ConfigClass(config.getcfg('FLASK')))
53 _set_logger(mjpapp)
54 return mjpapp
要使用这个全局 logger 对象,只需要进行下面的操作就可以了:
1
2from mjp.app import logger
3
4logger.info('Answer to Life, the Universe, and Everything: %s', 42)
我们看到,由于 Python 的动态特性和全局模块特性,这个使用方法是相当优雅和 Pythonic 的。
Golang 中的 log 包选择
在 Golang 实现中,我没有选择标准库中的 log 包,而是使用了 Logrus 这个 Golang 世界中被 Star 最多的 log 库,也是 docker 这个 Golang 明星项目使用的 logger 库。至于原因,看了下面的特性就清楚了 (via):
- 完全兼容golang标准库日志模块。logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集。如果你的项目使用标准库日志模块,完全可以用最低的代价迁移到logrus上。
- 可扩展的Hook机制。允许使用者通过hook方式,将日志分发到任意地方,如本地文件系统、标准输出、logstash、elasticsearch或者mq等,或者通过hook定义日志内容和格式等。
- 可选的日志输出格式。logrus内置了两种日志格式,JSONFormatter和TextFormatter。 如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式。
- Field机制。logrus鼓励通过Field机制进行精细化、结构化的日志记录,而不是通过冗长的消息来记录日志。
- logrus是一个可插拔的、结构化的日志框架。
可能是由于 Golang 标准库中的 log 包功能太弱,加上 Golang 社区的年轻活力,导致 Golang 世界中没有出现一个类似于 Java 世界的 log4j 这样一个具有统治力的库。这导致我们有大量优秀的 log 库可供选择,较真的话可能会挑花眼。有兴(Shi)趣(Jian)可以在 awesome-go 翻一下。
Logrus 已经帮我们处理的所有的事情,我要做的就是在项目中进行一下封装,把它做成一个单例:
1package util
2
3import (
4 "fmt"
5 "os"
6 "path"
7 "sync"
8
9 "github.com/gin-gonic/gin"
10 "github.com/sirupsen/logrus"
11)
12
13func init() {
14 logsDir = GetDir("logs")
15 os.Mkdir(logsDir, os.ModePerm)
16}
17
18// Logger is a global logger
19type Logger struct {
20 filename string
21 *logrus.Logger
22}
23
24var logsDir string
25var appLogger *Logger
26var appLoggerOnce sync.Once
27
28// GetAppLogger will start a global logger use in app
29func GetAppLogger() *Logger {
30 appLoggerOnce.Do(func() {
31 appLogger = createLogger()
32 })
33 return appLogger
34}
35
36func makeFile() (*os.File, string) {
37 // 在调试状态的时候输出到 stdout
38 if gin.IsDebugging() {
39 return os.Stdout, "os.Stdout"
40 }
41 // 非调试状态的时候输出的库 app.log
42 appLogFile := path.Join(logsDir, "app.log")
43 file, err := os.OpenFile(appLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
44 if err != nil {
45 panic(err)
46 }
47 return file, appLogFile
48}
49
50func createLogger() *Logger {
51 file, name := makeFile()
52 fmt.Printf("createLogger %s\n", name)
53 log := &logrus.Logger{
54 Out: file,
55 Formatter: new(logrus.JSONFormatter),
56 Level: logrus.DebugLevel,
57 }
58 return &Logger{name, log}
59}
在使用的时候,只需要这样处理:
1import "mjp/util"
2
3logger = util.GetAppLogger()
4logger.Infof("Answer to Life, the Universe, and Everything: %d", 42)
如果要频繁使用的话,可以把 logger
对象的赋值放到 init
中:
1package middlewares
2
3import (
4 "mjp/models"
5 "mjp/util"
6)
7
8var logger *util.Logger
9
10func init() {
11 logger = util.GetAppLogger()
12}
13
14// 使用 mjstObj 中的数据获取一个 admin
15func getAdmin(mjstObj *app.MJST) *models.AdminModel {
16 admin := &models.AdminModel{}
17 adminFindErr := models.AdminDB.First(admin, mjstObj.Uid).Error
18 if adminFindErr != nil {
19 logger.Errorf("getAdmin Error %s", adminFindErr)
20 return nil
21 }
22 return admin
23}
阅读系列所有文章:从 Flask 到 Gin。
全文完
- 文章ID:2683
- 原文作者:zrong
- 原文链接:https://blog.zengrong.net/post/flask-to-gin-logging/
- 版权声明:本作品采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可,非商业转载请注明出处(原文作者,原文链接),商业转载请联系作者获得授权。