程序内配置
import logging
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)
# print(os.path.join(results.output_dir, 'debug.log'))
fh = logging.FileHandler(os.path.join(results.output_dir, 'debug.log'))
fh.setLevel(logging.INFO)
sh = logging.StreamHandler()
sh.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
sh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(sh)
logger.debug(results.output_dir + 'debug.log')
config 配置
# 定义logger模块,root是父类,必需存在的,其它的是自定义。
# logging.getLogger(NAME)便相当于向logging模块注册了一种日志打印
# name 中用 . 表示 log 的继承关系
[loggers]
keys=root,infoLogger,errorLogger
# 定义handler
[handlers]
keys=infoHandler,errorHandler
# 定义格式化输出
[formatters]
keys=infoFmt,errorFmt
#--------------------------------------------------
# 实现上面定义的logger模块,必需是[logger_xxxx]这样的形式
#--------------------------------------------------
# [logger_xxxx] logger_模块名称
# level 级别,级别有DEBUG、INFO、WARNING、ERROR、CRITICAL
# handlers 处理类,可以有多个,用逗号分开
# qualname logger名称,应用程序通过 logging.getLogger获取。对于不能获取的名称,则记录到root模块。
# propagate 是否继承父类的log信息,0:否 1:是
[logger_root]
level=INFO
handlers=errorHandler
[logger_errorLogger]
level=ERROR
handlers=errorHandler
propagate=0
qualname=errorLogger
[logger_infoLogger]
level=INFO
handlers=infoHandler
propagate=0
qualname=infoLogger
#--------------------------------------------------
# handler
#--------------------------------------------------
# [handler_xxxx]
# class handler类名
# level 日志级别
# formatter,上面定义的formatter
# args handler初始化函数参数
[handler_infoHandler]
class=StreamHandler
level=INFO
formatter=infoFmt
args=(sys.stdout,)
[handler_errorHandler]
class=logging.handlers.TimedRotatingFileHandler
level=ERROR
formatter=errorFmt
# When computing the next rollover time for the first time (when the handler is created),
# the last modification time of an existing log file, or else the current time,
# is used to compute when the next rotation will occur.
# 这个功能太鸡肋了,是从handler被创建的时间算起,不能按自然时间 rotation 切分,除非程序一直运行,否则这个功能会有问题
# 临时解决方案参考下面的链接:Python 多进程日志记录
# http://blogread.cn/it/article/4175?f=wb2
args=('error.log', 'M', 1, 5)
#--------------------------------------------------
# 日志格式
#--------------------------------------------------
# %(asctime)s 年-月-日 时-分-秒,毫秒 2013-04-26 20:10:43,745
# %(filename)s 文件名,不含目录
# %(pathname)s 目录名,完整路径
# %(funcName)s 函数名
# %(levelname)s 级别名
# %(lineno)d 行号
# %(module)s 模块名
# %(message)s 消息体
# %(name)s 日志模块名
# %(process)d 进程id
# %(processName)s 进程名
# %(thread)d 线程id
# %(threadName)s 线程名
[formatter_infoFmt]
format=%(asctime)s %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
class=logging.Formatter
[formatter_errorFmt]
format=%(asctime)s %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
class=logging.Formatter
使用
- logconfig.ini
[loggers]
keys=root,infoLogger,errorLogger
[handlers]
keys=infoHandler,errorHandler
[formatters]
keys=infoFmt,errorFmt
[logger_root]
level=INFO
handlers=errorHandler,infoHandler
[logger_errorLogger]
level=ERROR
handlers=errorHandler
propagate=0
qualname=errorLogger
[logger_infoLogger]
level=INFO
handlers=infoHandler
propagate=0
qualname=infoLogger
[handler_infoHandler]
class=StreamHandler
level=INFO
formatter=infoFmt
args=(sys.stdout,)
[handler_errorHandler]
class=logging.handlers.TimedRotatingFileHandler
level=ERROR
formatter=errorFmt
args=('error.log', 'M', 1, 5)
[formatter_infoFmt]
format=%(asctime)s %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
class=logging.Formatter
[formatter_errorFmt]
format=%(asctime)s %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
class=logging.Formatter
- test.py
import logging
import logging.config
logging.config.fileConfig("logconfig.ini")
log = logging.getLogger("test")
log.info("info")
log.error("error")
log.warning("warning")
程序内使用字典
import logging
import logging.config
DEBUG = True
# 给过滤器使用的判断
class RequireDebugTrue(logging.Filter):
# 实现filter方法
def filter(self, record):
return DEBUG
log_config_dict = {
"version": 1,
'disable_existing_loggers': False, # 是否禁用现有的记录器
# 日志管理器集合
'loggers': {
# 管理器
'default': {
'handlers': ['console', 'log'],
'level': 'INFO',
'propagate': True, # 是否传递给父记录器
},
},
# 处理器集合
'handlers': {
# 输出到控制台
'console': {
'level': 'INFO', # 输出信息的最低级别
'class': 'logging.StreamHandler',
'formatter': 'standard', # 使用standard格式
'filters': ['require_debug_true', ], # 仅当 DEBUG = True 该处理器才生效
},
# 输出到文件
'log': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'standard',
'filename': "test.log", # 输出位置
'maxBytes': 1024 * 1024 * 5, # 文件大小 5M
'backupCount': 5, # 备份份数
'encoding': 'utf8', # 文件编码
},
},
# 过滤器
'filters': {
'require_debug_true': {
'()': RequireDebugTrue,
}
},
# 日志格式集合
'formatters': {
# 标准输出格式
'standard': {
# [具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容
'format': '[%(asctime)s][%(threadName)s:%(thread)d][%(name)s:%(levelname)s(%(lineno)d)]--[%(module)s:%(funcName)s]:%(message)s'
}
}
}
logging.config.dictConfig(log_config_dict)
logger_info = logging.getLogger("default")
YMAL 配置
- logconfig.yaml
version: 1
disable_existing_loggers: False
formatters:
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
info_file_handler:
class: logging.handlers.TimedRotatingFileHandler
level: INFO
formatter: simple
filename: info.log
when: M
interval: 1
backupCount: 10
encoding: utf8
error_file_handler:
class: logging.handlers.RotatingFileHandler
level: ERROR
formatter: simple
filename: errors.log
maxBytes: 10485760
backupCount: 20
encoding: utf8
loggers:
my_module:
level: ERROR
handlers: [info_file_handler]
propagate: no
root:
level: INFO
handlers: [console,info_file_handler,error_file_handler]
- test.py
import logging
import logging.config
import yaml
with open("logconfig.yaml", "r", encoding="utf-8") as f:
logging.config.dictConfig(yaml.safe_load(f.read()))
log = logging.getLogger("test")
log.info("info")
log.warning("warning")
log.error("error")
Handler
作为 Handler
基类的补充,提供了很多有用的子类:
StreamHandler
实例发送消息到流(类似文件对象)。FileHandler
实例将消息发送到硬盘文件。BaseRotatingHandler
是轮换日志文件的处理程序的基类。它并不应该直接实例化。而应该使用RotatingFileHandler
或TimedRotatingFileHandler
代替它。RotatingFileHandler
实例将消息发送到硬盘文件,支持最大日志文件大小和日志文件轮换。TimedRotatingFileHandler
实例将消息发送到硬盘文件,以特定的时间间隔轮换日志文件。SocketHandler
实例将消息发送到 TCP/IP 套接字。从 3.4 开始,也支持 Unix 域套接字。DatagramHandler
实例将消息发送到 UDP 套接字。从 3.4 开始,也支持 Unix 域套接字。SMTPHandler
实例将消息发送到指定的电子邮件地址。SysLogHandler
实例将消息发送到 Unix syslog 守护程序,可能在远程计算机上。NTEventLogHandler
实例将消息发送到 Windows NT/2000/XP 事件日志。MemoryHandler
实例将消息发送到内存中的缓冲区,只要满足特定条件,缓冲区就会刷新。HTTPHandler
实例使用GET
或POST
方法将消息发送到 HTTP 服务器。WatchedFileHandler
实例会监视他们要写入日志的文件。如果文件发生更改,则会关闭该文件并使用文件名重新打开。此处理程序仅在类 Unix 系统上有用; Windows 不支持依赖的基础机制。QueueHandler
实例将消息发送到队列,例如在queue
或multiprocessing
模块中实现的队列。NullHandler
实例对错误消息不执行任何操作。它们由想要使用日志记录的库开发人员使用,但是想要避免如果库用户没有配置日志记录,则显示 "无法找到记录器XXX的消息处理器" 消息的情况。有关更多信息,请参阅 配置库的日志记录 。
问题
多进程写文件
在使用TimeRotaingFileHandler
的doRollover
方法时,每个进程在过了rotate时间点之后写新日志之前,都会执行doRollover
方法:
- 检查前一刻的文件是否存在,存在的话删除
- 将正在写的文件更名为前一刻的时间
- 往新的日志写入
因为多进程执行的不确定性,会出现上个时刻的日志被删除,当前时刻的日志也会有一部分被删除。
日志在写入文件时,因为磁盘的刷新策略并不会实时的将缓冲区的数据刷新到磁盘中,此时如果多个进程同时写入同一个文件的话,就会出现日志混乱的问题,linux允许多个进程打开一个文件,但windows不行。
使用进程号和时间进行区分
将进程id作为日志的一部分,则每个进程的日志操作就不会影响了,但是当重启服务器时则会取得新的进程id,而且日志中出现进程id,会感觉有点乱,可以用crontab执行脚本,来合并日志。
使用单独的程序来接收日志
使用SocketHandler
,将日志通过网络发送出去。