加载中...

logging日志模块


logging日志模块

什么是日志

日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性,重要性也被称为等级严重性

什么时候使用日志

日志(logging)模块提供了一系列的函数(debug、info、warning、critical)来适应不同的应用场景。想要决定何时使用日志,请看下表,其中显示了对于每个通用任务集合来说最好的工具。

想要执行的任务 此任务最好的工具
对于命令行或程序的应用,结果显示在控制台 print()
在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查) logging.info() 函数(当有诊断目的需要详细输出信息时使用 logging.debug() 函数)
提出一个警告信息基于一个特殊的运行时事件 warnings.warn() 位于代码库中,该事件是可以避免的,需要修改客户端应用以消除告警。logging.warning() 不需要修改客户端应用,但是该事件还是需要引起关注
对一个特殊的运行时事件报告错误 引发异常
报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理) logging.error(), logging.exception()logging.critical() 分别适用于特定的错误及应用领域

下表展示了logging中,日志功能应以所追踪事件级别或严重性而定。各级别适用性如下(以严重性递减):

等级 数值 描述
CRITICAL 50 严重的错误,表明程序已经不能继续执行
FATAL(不推荐) 50 FATAL是CRITICAL的别名。
ERROR 40 由于严重的问题,程序的某些功能已经不能正常执行
WARNING 30 表明已经或即将发生的意外(例如磁盘空间不足)。但程序仍按照预期运行
WARN(不推荐) 30 WARN是WANING的简写形式。
INFO 20 确认程序按预期运行
DEBUG 10 细节信息,在我们调试程序是使用
NOTSET 0 不设置

默认的级别是WARNING,意味着只会追踪该级别及以上的事件,除非更改日志配置。

日志的使用

简单使用

日志模块的本质使用流程:

# 创建一个logger对象
# 创建一个文件操作符(用来存储日志信息)
# 创建一个屏幕操作符(用来在屏幕打印信息)
# 创建一个格式(要写的日志或屏幕显示内容的格式:是否带有文件名或日期等)
 
# 给logger对象绑定 文件操作符
# 给logger对象绑定 屏幕操作符 
# 给文件操作符 设定格式
# 给屏幕操作符 设定格式
 
# 用logger对象来操作

示例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import logging
 
logger = logging.getLogger() # 创建一个logger对象
fh = logging.FileHandler("log.log") # 创建一个文件操作符
sh = logging.StreamHandler() # 创建一个屏幕操作符
fm = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 创建一个格式
 
logger.addHandler(fh) # 给logger对象绑定 文件操作符
logger.addHandler(sh) # 给logger对象绑定 屏幕操作符
fh.setFormatter(fm) # 给文件操作符绑定 格式
sh.setFormatter(fm) # 给屏幕操作符绑定 格式
 
logger.error("出错了") # 输出错误信息
# logger.error("出错了",exc_info=True) # 输出错误信息,如果exc_info为True,在真正的出错日志记录中将保留详细的堆栈信息

输出结果:

2019-11-03 17:20:59,569 - root - ERROR - 出错了

使用basicConfig方法写日志

首先看一下basicConfig方法都可以指定哪些参数:

  • filename,即日志输出的文件名,如果指定了这个信息之后,实际上会启用``FileHandler,而不再是StreamHandler`,这样日志信息便会输出到文件中了。

  • filemode,日志文件写入方式,可以是wa,默认的是a模式。

  • format,指定日志信息的输出格式,详细参考,这里列出常用的参数:

    • %(levelno)s:打印日志级别的数值。
    • %(levelname)s:打印日志级别的名称。
    • %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]。
    • %(filename)s:打印当前执行程序名。
    • %(funcName)s:打印日志的当前函数。
    • %(lineno)d:打印日志的当前行号。
    • %(asctime)s:打印日志的时间。
    • %(thread)d:打印线程ID。
    • %(threadName)s:打印线程名称。
    • %(process)d:打印进程ID。
    • %(processName)s:打印进程名称。
    • %(module)s:打印模块名称。
    • %(message)s:打印日志信息。
  • datefmt,指定时间的输出格式。

  • style,如果format指定,该参数可以指定格式化时的占位符。例如'{''$'用于printf风格str.format()string.Template分别。默认为'%'。3.2版本新增参数。

  • level,指定日志输出的类别,程序会输出大于等于此级别的信息。

  • stream,在没有指定filename的时候会默认使用StreamHandler,这时stream可以指定初始化的文件流。

  • handlers:可以指定日志处理时所使用的 Handlers,必须是可迭代的。

示例:

import logging

file_handler = logging.FileHandler(filename="test.log", mode="a", encoding="utf-8")
stream_handler = logging.StreamHandler()

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(process)d: %(message)s',
    datefmt='%Y-%m-%d %H-%M-%S %p',
    handlers=[file_handler, stream_handler],
    level=logging.ERROR
)
logging.error("aaaaaa")

需要注意的是,logging.basicConfig只生效一次,比如:

import logging
logging.basicConfig(
    filename='test1.log',
    filemode='w',
    level=logging.DEBUG
)

# 无效
logging.basicConfig(
    filename='test2.log',
    filemode='a',
    level=logging.INFO
)

logging.debug('debug level: 10')

正如上例所示,我们配置了两次basicConfig。但如果运行你会发现,只有第一个配置生效了,第二个配置不会生效。原因是当在第一次配置的时候,logging在内部就会进行配置,第二次再次配置的时候,logging就会认为我已经配置好了,不需要再次配置了。

使用配置文件和fileConfig配置日志

首先新建配置文件logging.conf,用来存放logging配置文件的信息:

[loggers]
# 配置logger信息。必须包含一个名字叫做root的logger,当使用无参函数logging.getLogger()时,默认返回root这个logger,其他自定义logger可以通过 logging.getLogger("fileAndConsole") 方式进行调用
keys=root,file,fileAndConsole

[handlers]
# 定义声明handlers信息。
keys=fileHandler,consoleHandler

[formatters]
# 设置日志格式
keys=simpleFormatter

[logger_root]
# logger_XXX对loggers中声明的logger进行逐个配置,且要一一对应,在所有的logger中,必须制定lebel和handlers这两个选项,对于非roothandler,还需要添加一些额外的option,其中qualname表示它在logger层级中的名字,在应用代码中通过这个名字制定所使用的handler,即 logging.getLogger("fileAndConsole"),handlers可以指定多个,中间用逗号隔开,比如handlers=fileHandler,consoleHandler,同时制定使用控制台和文件输出日志
level=DEBUG
handlers=consoleHandler
[logger_file]
level=DEBUG
handlers=fileHandler
qualname=file
propagate=1
[logger_fileAndConsole]
level=DEBUG
handlers=fileHandler,consoleHandler
qualname=fileAndConsole
propagate=0   # # 通常我们都需要显示的指定propagate的值为0,防止日志记录向上层logger传递

[handler_consoleHandler]
# handler_xxx在handler中,必须指定class和args这两个option
# 常用的class包括 StreamHandler(仅将日志输出到控制台)、FileHandler(将日志信息输出保存到文件)、RotaRotatingFileHandler(将日志输出保存到文件中,并设置单个日志文件的大小和日志文件个数
# args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;里面指定输出路径,比如输出的文件名称等
# level与logger中的level一样
# 而formatter指定的是该处理器所使用的格式器,这里指定的格式器名称必须出现在formatters这个section中,且在配置文件中必须要有这个formatter的section定义;如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间、日志器名称等信息;
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter

[handler_fileHandler]
class=FileHandler
args=('test.log', 'a')
level=DEBUG
formatter=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(process)d: %(message)s
datefmt=%Y-%m-%d %H:%M:%S %p

log日志的使用:

import logging.config

logging.config.fileConfig("logging.conf")

logger = logging.getLogger("simpleExample")

logger.error("aaaaaa")

常用配置:

[loggers]
keys=root,simpleExample

[handlers]
keys=fileHandler,consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=fileHandler,consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=INFO
formatter=simpleFormatter

[handler_fileHandler]
class=FileHandler
args=('logging.log', 'a')
level=ERROR
formatter=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s : %(message)s
datefmt=%Y-%m-%d %H:%M:%S %P

使用字典配置信息和dictConfig()函数实现日志配置

Python 3.2中引入的一种新的配置日志记录的方法–用字典来保存logging配置信息。这相对于上面所讲的基于配置文件来保存logging配置信息的方式来说,功能更加强大,也更加灵活,因为我们可把很多的数据转换成字典。比如,我们可以使用JSON格式的配置文件、YAML格式的配置文件,然后将它们填充到一个配置字典中;

import logging
import logging.config

logging_config = dict(
    version=1,      # 必填项
    formatters={    # 日志格式化
        'simple': {
            'format': '%(asctime)s - %(levelname)s: %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S'
        },
        'output': {
            'format': '%(asctime)s - %(levelname)s: %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S'
        }
    },
    handlers={     # handler
        'default_handlers': {
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'export.log',
            'maxBytes': 1024 * 1024 * 20,
            'backupCount': 10,
            'level': 'DEBUG',
            'formatter': 'simple'
        },
        'output_handlers': {
            'class': 'logging.StreamHandler',
            'formatter': 'output',
            'level': 'DEBUG'
        }
    },
    root={
        'handlers': ['default_handlers', 'output_handlers'],
        'level': logging.DEBUG,
    },
)
logging.config.dictConfig(logging_config)
logger = logging.getLogger(__name__)

logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

# 输出结果:
# 2024-12-03 17:02:24 - DEBUG: debug message
# 2024-12-03 17:02:24 - INFO: info message
# 2024-12-03 17:02:24 - WARNING: warn message
# 2024-12-03 17:02:24 - ERROR: info message
# 2024-12-03 17:02:24 - CRITICAL: info message

1. 关于dictConfig()函数的说明

该函数实际上是对configparser模块的封装,关于configparser模块的介绍请参考<

函数定义:该函数定义在 loging.config 模块下:

logging.config.dictConfig(config)

该函数可以从一个字典对象中获取日志配置信息,config参数就是这个字典对象。关于这个字典对象的内容规则会在下面进行描述。

2. 配置字典说明

无论是上面提到的配置文件,还是这里的配置字典,它们都要描述出日志配置所需要创建的各种对象以及这些对象之间的关联关系。比如,可以先创建一个名额为“simple”的格式器formatter;然后创建一个名为“default_handlers”的处理器handler,并指定该handler输出日志所使用的格式器为”simple”;使用root logger配置作用全局,并指定它所使用的处理器为”default_handlers”。

传递给dictConfig()函数的字典对象只能包含下面这些keys,其中version是必须指定的key,其它key都是可选项:

key名称 描述
version 必选项,其值是一个整数值,表示配置格式的版本,当前唯一可用的值就是1
formatters 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的格式器名称,value为格式器的配置信息组成的dict,如format和datefmt
filters 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的过滤器名称,value为过滤器的配置信息组成的dict,如name
handlers 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的处理器名称,value为处理器的配置信息组成的dcit,如class、level、formatter和filters,其中class为必选项,其它为可选项;其他配置信息将会传递给class所指定的处理器类的构造函数,如下面的handlers定义示例中的stream、filename、maxBytes和backupCount等
loggers 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的日志器名称,value为日志器的配置信息组成的dcit,如level、handlers、filters 和 propagate(yes
root 可选项,这是root logger的配置信息,其值也是一个字典对象。除非在定义其它logger时明确指定propagate值为no,否则root logger定义的handlers都会被作用到其它logger上
incremental 可选项,默认值为False。该选项的意义在于,如果这里定义的对象已经存在,那么这里对这些对象的定义是否应用到已存在的对象上。值为False表示,已存在的对象将会被重新定义。
disable_existing_loggers 可选项,默认值为True。该选项用于指定是否禁用已存在的日志器loggers,如果incremental的值为True则该选项将会被忽略

总结示例

通过之前日志的不同应用,我们可以总结出来一份拿走就用的配置:

import sys
import logging
def logger_configure(*args, fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                     datefmt='%Y/%m/%d %H:%M:%S',
                     level=logging.INFO
                     ):
    logging.basicConfig(
        format=fmt,
        datefmt=datefmt,
        level=level,
        handlers=list(args)
    )
if __name__ == '__main__':
    file_handler = logging.FileHandler('x1.log', mode='a', encoding='utf8')
    stream_handler = logging.StreamHandler(stream=sys.stdout)
    logger_configure(file_handler, stream_handler)
    logging.info('info level: 20')

上例中,我们将logging的配置封装成函数,给一些默认的配置参数,也可以手动配置。而args则接收多个输出模式。当我们在使用的时候直接调用该函数,并传递输出模式即可。

示例:

import logging


class LoggerHandler:

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(LoggerHandler, cls).__new__(cls)
        return cls._instance

    def __init__(self, log_name, log_level, file_path, stream_level='info', file_level='warning'):
        self.logger_name = log_name
        self.logger_level = log_level
        self.file_path = file_path
        self.stream_level = stream_level
        self.file_level = file_level

        # 创建日志对象
        self.logger = logging.getLogger(self.logger_name)
        # 设置默认日志级别
        self.logger.setLevel(self.logger_level)
        # 设置日志输出流
        to_stream = logging.StreamHandler()
        to_file = logging.FileHandler(self.file_path)
        # 设置日志输出级别
        to_stream.setLevel(self.stream_level)
        to_file.setLevel(self.file_level)
        # 设置日志输出格式
        formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
        to_stream.setFormatter(formatter)
        to_file.setFormatter(formatter)
        self.logger.addHandler(to_stream)
        self.logger.addHandler(to_file)


log = LoggerHandler(
    log_name='log',
    log_level=logging.INFO,
    file_path='./log.log',
    stream_level=logging.INFO,
    file_level=logging.WARNING
)

log.logger.info('info')
log.logger.error('error')
log.logger.warning('warning')

在Django中使用

Django日志

Django使用python内建的logging模块打印日志,Python的logging配置由四个部分组成:

  1. 记录器(Logger)
  2. 处理程序(Handler)
  3. 过滤器(Filter)
  4. 格式化(Formatter)

Django中默认使用python内置的logging模块。

Logger 记录器

Logger 是日志系统的入口。每个 logger 都是命名了的 bucket, 消息写入 bucket 以便进一步处理。

Logger 可以配置 日志级别。日志级别描述了由该 logger 处理的消息的严重性。Python 定义了下面几种日志级别:

  • DEBUG:排查故障时使用的低级别系统信息
  • INFO:一般的系统信息
  • WARNING:描述系统发生了一些小问题的信息
  • ERROR:描述系统发生了大问题的信息
  • CRITICAL:描述系统发生严重问题的信息

每一条写入 logger 的消息都是一条日志记录。每一条日志记录也包含 日志级别,代表对应消息的严重程度。日志记录还包含有用的元数据,来描述被记录的事件细节,例如堆栈跟踪或者错误码。

当 logger 处理一条消息时,会将自己的日志级别和这条消息的日志级别做对比。如果消息的日志级别匹配或者高于 logger 的日志级别,它就会被进一步处理。否则这条消息就会被忽略掉。

当 logger 确定了一条消息需要处理之后,会把它传给 Handler。

Django 内置记录器

类功能描述
django.request 请求处理相关的日志消息。5xx响应被提升为错误消息;4xx响应被提升为警告消息。
django.server 由RunServer命令调用的服务器所接收的请求的处理相关的日志消息。HTTP 5XX响应被记录为错误消息,4XX响应被记录为警告消息,其他一切都被记录为INFO。
django.template 与模板呈现相关的日志消息
django.db.backends 有关代码与数据库交互的消息。例如,请求执行的每个应用程序级SQL语句都在调试级别记录到此记录器。

Handler 处理程序

Handler 是决定如何处理 logger 中每一条消息的引擎。它描述特定的日志行为,比如把消息输出到屏幕、文件或网络 socket。

和 logger 一样,handler 也有日志级别的概念。如果一条日志记录的级别不匹配或者低于 handler 的日志级别,对应的消息会被 handler 忽略。

一个 logger 可以有多个 handler,每一个 handler 可以有不同的日志级别。这样就可以根据消息的重要性不同,来提供不同格式的输出。例如,你可以添加一个 handler 把 ERROR 和 CRITICAL 消息发到寻呼机,再添加另一个 handler 把所有的消息(包括 ERROR 和 CRITICAL 消息)保存到文件里以便日后分析。

Logging 自身携带Handler
logging模块提供了一些处理器,可以通过各种方式处理日志消息。

类名 功能描述
logging.StreamHandler 类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息
logging.FileHandler 将日志消息写入文件filename。
logging.handlers.DatagramHandler(host,port) 发送日志消息给位于制定host和port上的UDP服务器。使用UDP协议,将日志信息发送到网络
logging.handlers.HTTPHandler(host, url) 使用HTTP的GET或POST方法将日志消息上传到一台HTTP 服务器。
logging.handlers.RotatingFileHandler(filename) 将日志消息写入文件filename。如果文件的大小超出maxBytes制定的值,那么它将被备份为filenamel。
logging.handlers.TimedRotatingFileHandler(log_path, when=’D’, encoding=’utf-8’) 将日志消息写入文件filename。按日期进行文件切分。
logging.handlers.SocketHandler 使用TCP协议,将日志信息发送到网络。
logging.handlers.SysLogHandler 日志输出到syslog
logging.handlers.NTEventLogHandler 远程输出日志到Windows NT/2000/XP的事件日志
logging.handlers.SMTPHandler 远程输出日志到邮件地址
logging.handlers.MemoryHandler 日志输出到内存中的制定buffer

注意:由于内置处理器还有很多,如果想更深入了解。可以查看官方手册。

Filter过滤器

在日志从 logger 传到 handler 的过程中,使用 Filter 来做额外的控制。

默认情况下,只要级别匹配,任何日志消息都会被处理。不过,也可以通过添加 filter 来给日志处理的过程增加额外条件。例如,可以添加一个 filter 只允许某个特定来源的 ERROR 消息输出。

Filter 还被用来在日志输出之前对日志记录做修改。例如,可以写一个 filter,当满足一定条件时,把日志记录从 ERROR 降到 WARNING 级别。

Filter 在 logger 和 handler 中都可以添加;多个 filter 可以链接起来使用,来做多重过滤操作。

Formatter格式化

formatter 的参数就简单一点,通过不同的 key 来区分不同的 formatter;

对于 format 的定义的参数还有很多,以下是几个常用的汇总:

参数名称 参数用法 含义
levelname %(levelname)s 日志等级
message %(message)s 消息内容
asctime %(asctime)s 时间,格式为’2022-01-01 00:00:00,000’
pathname %(pathname)s 日志输出所在文件的全路径
filename %(filename)s 日志输出所在的文件名, pathname 的文件名部分。
module %(module)s 日志输出所在的模块,可以理解成不带后缀的文件名, filename 的名称部分
name %(name)s 调用日志使用的名称,logging.getLogger(name)时为从模块到函数,比如 blog.views
funcName %(funcName)s 日志输出的函数名称
lineno %(lineno)d 日志输出所在的文件的行数
process %(process)d 进程id
processName %(processName)s 进程名称
thread %(thread)d 线程id
threadName %(threadName)s 线程名称
relativeCreated %(relativeCreated)d 以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。
stack_info 此属性不需要用户进行格式化。 当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。

settings中完整的配置

如果想自定义配置日志信息,我们可以在settings.py文件中配置,那配置的格式是怎么样的呢?我们可以通过from django.utils.log import DEFAULT_LOGGING查看Django中默认的日志配置信息,然后依葫芦画瓢即可

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,  
    'incremental':True,
    'filters': {},
    'formatters': {},
    'handlers': {},
    'loggers': {}
}

上述是默认的日志配置信息的格式,我们依次介绍

  • version:配置信息的版本
  • disable_existing_loggers:默认为True,True:设置已存在的logger失效。False:让已存在的logger不失效,保证日志信息完整。一般情况下设置为False
  • incremental:默认为False。True:是将配置解释为现有配置的增量。False:配置会覆盖已有默认配置。一般此项不用配置
  • filter:过滤器
  • formatters:格式器
  • handlers:处理器
  • loggers:日志器

我的配置示例:

import os

os.environ.setdefault('DJANGO_LOG_FOLDER', './logs')
os.environ.setdefault('DJANGO_LOG_LEVEL', 'INFO')

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "verbose": {
            "format": "[{levelname}] [{asctime}] {name} Line:{lineno:d} {processName:s} {threadName:s} :{message}",
            "style": "{",
        },
        "simple": {
            "format": "[{asctime}] {message}",
            "style": "{",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "verbose",
        },
        "file": {
            "level": "INFO",
            "class": "logging.handlers.RotatingFileHandler",
            "filename":
                os.path.join(os.getenv("DJANGO_LOG_FOLDER", "./logs"),
                             "django_log.log"),
            "formatter": "verbose",
            'maxBytes': 20 * 1024 * 1024,
            'backupCount': 10,
            'encoding': 'utf-8',
            'delay': True  # 解决日志报错说已被使用,进程无法访问的问题。
        },
    },
    "loggers": {
        "": {    # 接收所有log,包括用户自定义log
            "handlers": ["console", "file"],
            "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"),
            "propagate": False,
        },
        "django": {
            "handlers": [],
            "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"),
            "propagate": True,
        },
    },
}

在views.py中使用:

import logging

logger = logging.getLogger(__name__) # 也可以指定logger名使用指定日志, 如下示例:
# logger = logging.getLogger("django")

class DemoView(APIView):
    def get(self,request):
        logger.info("this is Demo get view.")
        ......

遇到的问题及解决方案

按时间分割和文件大小分割记录日志报错问题

问题描述RotatingFileHandlerTimedRotatingFileHandler在生成新的文件时,会抛出 PermissionError 异常(其它分割文件的也同样会出现) 。

--- Logging error ---
Traceback (most recent call last):
  File "D:\software\Python38\lib\logging\handlers.py", line 70, in emit
    self.doRollover()
  File "D:\software\Python38\lib\logging\handlers.py", line 171, in doRollover
    self.rotate(self.baseFilename, dfn)
  File "D:\software\Python38\lib\logging\handlers.py", line 111, in rotate
    os.rename(source, dest)
PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。: 'C:\\Users\\User\\Desktop\\workspaces\\project\\Bill\\BillServer\\logs\\bill_log.log' -> 'C:\\Users\\User\\Desktop\\workspaces\\project\\Bill\\BillServer\\logs\\bill_log.log.1'
Call stack:
  File "D:\software\Python38\lib\threading.py", line 890, in _bootstrap
    self._bootstrap_inner()
  File "D:\software\Python38\lib\threading.py", line 932, in _bootstrap_inner
  ......

原因分析:通过查看资源管理器发现创建应用会启用两个python进程,django默认会开启两个进程:第一个是reload自动重载,第二个才是Django主应用.

解决方案

  • 方案一:

    在manage文件后面加启动参数 –noreload  关闭自动重载

    python manage.py runserver --noreload

    可以看到关闭自动重载后,python主进程之开了一个,而且没有报错

    缺点:一棒子打死,别的代码修改也不能自动重载,只能手动重启

  • 方案二:

    使用模块concurrent_log_handler模块

    pip install concurrent_log_handler #安装模块
    
    #setting.py
    import concurrent_log_handler    #导入模块
     
    ······
     
    LOGGING = {
        ······
        'handlers': {
            ······
            },
            'file': {
                'level': 'INFO',
                'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler', # 修改这里
                'filename': os.path.join(BASE_LOG_DIR, "django_log.log"), 
                'maxBytes': 1024 * 1024 * 50,
                'backupCount': 3, 
                'formatter': 'standard',
                'encoding': 'utf-8',
            },
    	......
    }

    concurrent_log_handler适用于windows系统

    linux需要安装ConcurrentLogHandler

    缺点:需要安装三方库,没有TimedRotatingFileHandler

  • 方案三

    使用进程id 命名log文件

    #setting.py
    import os
     
    BASE_LOG_DIR = os.path.join(BASE_DIR, "log")
    loggerpid = str(os.getpid())
    LOGGING = {
        ······
            'timed_file': {
            ······
                'filename': os.path.join(BASE_LOG_DIR,f"logging_study_timed{loggerpid}.log"),
                ······
            },
    }
  • 方案四

    添加delay参数为True

    ......
    LOGGING = {
        ······
        "handlers": {
            "file": {
                "level": "DEBUG",
                "class": "logging.handlers.RotatingFileHandler",
                ......
                'delay': True  # 解决日志报错说已被使用,进程无法访问的问题。
            },
        },
    }
    ......

文章作者: 无夜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 无夜 !
评论
  目录