Flask如何使用logging.FileHandler将日志保存到文件

需求

将日志尽可能往文件中输,自带的默认只输出到屏幕上。

代码

获取文件名

def get_custom_file_name():
    def make_dir(make_dir_path):
        path = make_dir_path.strip()
        if not os.path.exists(path):
            os.makedirs(path)
        return path
    log_dir = "ac_logs"
    file_name = 'logger-' + time.strftime('%Y-%m-%d', time.localtime(time.time())) + '.log'
    file_folder = os.path.abspath(os.path.dirname(__file__)) + os.sep + log_dir
    make_dir(file_folder)
    return file_folder + os.sep + file_name

配置logging

dictConfig({
    'version': 1,
    'formatters': {'default': {
        'format': '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s',
    }},
    'handlers': {
        'default': {
            'class': 'logging.StreamHandler',
            'stream': 'ext://flask.logging.wsgi_errors_stream',
            'formatter': 'default'
        },
        'custom': {
            'class' : 'logging.FileHandler',
            'formatter': 'default',
            'filename' : get_custom_file_name(),
            'encoding' : 'utf-8'
        },
    },
    'root': {
        'level': 'INFO',
        'handlers': ['custom']
    }
})

代码分析

在官方文档中,有一个默认的handler,当我添加一个自定义的handler,名叫custom的时候,读取配置失败,程序中断,也就无法继续执行下去,提示说,少一个叫做filename的参数

loggin.FileHandler的构造函数中,有一个必填的参数,叫做filename。如下:

class FileHandler(StreamHandler):
    """
    A handler class which writes formatted logging records to disk files.
    """
    def __init__(self, filename, mode='a', encoding=None, delay=False):
        """
        Open the specified file and use it as the stream for logging.
        """
        # Issue #27493: add support for Path objects to be passed in
        filename = os.fspath(filename)
        #keep the absolute path, otherwise derived classes which use this
        #may come a cropper when the current directory changes
        self.baseFilename = os.path.abspath(filename)
        self.mode = mode
        self.encoding = encoding
        self.delay = delay

如何才能传入参数filename?

①进入dictConfig()

def dictConfig(config):
    """Configure logging using a dictionary."""
    dictConfigClass(config).configure()

②进入configure(),找到对handler的处理逻辑,如下:

# 前面的代码省略
# 获取handlers
handlers = config.get('handlers', EMPTY_DICT)
deferred = []
# 遍历其中的每一个handler
for name in sorted(handlers):
    try:
    	# 具体处理每一个handler
        handler = self.configure_handler(handlers[name])
        handler.name = name
        handlers[name] = handler
    except Exception as e:
        if 'target not configured yet' in str(e.__cause__):
            deferred.append(name)
        else:
            raise ValueError('Unable to configure handler '
                             '%r' % name) from e
# 后面的代码省略

③进入具体处理handler的逻辑,self.configure_handler()

def configure_handler(self, config):
    # 省略代码若干行 ...
    if '()' in config:
        c = config.pop('()')
        if not callable(c):
            c = self.resolve(c)
        factory = c
    else:
        cname = config.pop('class')
        klass = self.resolve(cname)
        # 省略代码若干行 ...
        # 此处的kclass就是配置的class名所对应的类
        factory = klass
    props = config.pop('.', None)
    # 读取完类名,获取到该类、获取了formatter之后,接着读取conf里面的数据
    # 在这里将剩下所定义的参数,弄成dict类型的数据
    kwargs = {k: config[k] for k in config if valid_ident(k)}
    try:
    	# 直接将dict类型的数据作为函数入参,实例化出一个FileHandler
        result = factory(**kwargs)
    except TypeError as te:
        if "'stream'" not in str(te):
            raise

其中的调试数据截图如下:

在这里插入图片描述

如果没有配置filename的信息,那么实例化类的时候就报错也是理所应当,因此还可以尝试性地配置encoding参数到其中,工作正常。

总结

虽然对这一块的整体了解还不够,但是对于能够参照官方文档,参考源代码实现,完成自己的想法,做到的那一刻还是有点成就感。

【参考】官方文档:http://flask.pocoo.org/docs/dev/logging

Read more

Volcano 与 Kubernetes GPU 调度学习笔记

本笔记系统整理 Volcano 调度器、Kubernetes 调度框架、GPU Device Plugin、HAMi 等云原生 AI 调度领域的核心知识,适合用于学习、复习和工程实践参考。 目录 * 第一部分:Volcano 入门 * 1. Volcano 是什么 * 2. 安装与快速使用 * 3. 核心特性一览 * 第二部分:Volcano 整体架构 * 4. Volcano 解决的核心问题 * 5. 整体架构与数据流 * 6. 三层抽象模型 * 第三部分:Volcano 核心实现原理 * 7. Session 机制 * 8. Gang Scheduling 实现 * 9. Queue 与 DRF 公平调度

容器镜像(4):镜像的常用工具箱

容器镜像(4):镜像的常用工具箱

前几篇在讲多架构镜像时已经用过 skopeo 和 crane 做镜像复制,这篇系统整理这两个工具的完整能力,同时介绍几个日常操作镜像时同样好用的工具。 一、skopeo:不依赖 Daemon 的镜像瑞士军刀 skopeo 的核心价值是绕过 Docker daemon,直接与 Registry API 交互。上一篇用它做镜像复制和离线传输,但它的能力远不止于此。 1.1 安装 # Ubuntu / Debian sudo apt install -y skopeo skopeo --version # skopeo version 1.15.1 1.2 inspect:免拉取检查镜像元数据 docker inspect 需要先把镜像拉到本地,skopeo inspect 直接向 Registry

容器镜像(3):多架构镜像构建

容器镜像(3):多架构镜像构建

一、什么是多架构镜像 1.1 OCI Image Index 上一篇介绍了单平台镜像的结构:一个 Manifest 指向 Config 和若干 Layer blob。多架构镜像在此之上多了一层——OCI Image Index(也叫 Manifest List),是一个轻量的索引文件,把多个单平台 Manifest 组织在一起: $ docker manifest inspect golang:1.22-alpine { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests&

容器镜像(2):containerd 视角下的镜像

容器镜像(2):containerd 视角下的镜像

一、为什么需要了解 containerd 如果你只用 docker run 跑容器,从来不关心底层,那可以不了解 containerd。但如果你在用 Kubernetes,或者想真正理解"容器运行时"是什么,containerd 是绕不开的。 事实上,当你执行 docker run 的时候,containerd 早就在后台悄悄工作了——Docker 从 1.11 版本开始,就把核心运行时剥离出来交给 containerd 负责。 1.1 Docker 的架构演变 早期的 Docker(1.10 及之前)是一个"大一统"的单体程序:一个 dockerd