python 跨模块实现按照文件大小,日期实现日志分割,反转
笔者的一个自动化测试平台项目,采用了python作为后端服务器语言。项目基于快速成型目的,写了一个极其简陋的日志记录功能,支持日志记录到文件和支持根据日志级别在终端打印不同颜色的log。但随着测试平台上线运行,发现日志文件大小急剧膨胀,运行一段时间,往往一个log能有几个G大小,而且也不能根据日期查看日志内容。基于根据文件大小和日志实现日志分割,在下查阅了不少前辈的资料,不断尝试,终于得出一个可以用的demo,在此分享也做个记录,不足之处,还望指正。
这是本人工作前辈的初始版本:
#!/usr/bin/python
# -*- coding: utf-8 -*- import os
import time
import logging
import platform
if platform.system() == 'Windows':
from ctypes import windll, c_ulong def color_text_decorator(function):
def real_func(self, string):
windll.Kernel32.GetStdHandle.restype = c_ulong
h = windll.Kernel32.GetStdHandle(c_ulong(0xfffffff5))
if function.__name__.upper() == 'ERROR':
windll.Kernel32.SetConsoleTextAttribute(h, 12)
elif function.__name__.upper() == 'WARN':
windll.Kernel32.SetConsoleTextAttribute(h, 13)
elif function.__name__.upper() == 'INFO':
windll.Kernel32.SetConsoleTextAttribute(h, 14)
elif function.__name__.upper() == 'DEBUG':
windll.Kernel32.SetConsoleTextAttribute(h, 15)
else:
windll.Kernel32.SetConsoleTextAttribute(h, 15)
function(self, string)
windll.Kernel32.SetConsoleTextAttribute(h, 15)
return real_func
else:
def color_text_decorator(function):
def real_func(self, string):
if function.__name__.upper() == 'ERROR':
self.stream.write('\033[0;31;40m')
elif function.__name__.upper() == 'WARN':
self.stream.write('\033[0;35;40m')
elif function.__name__.upper() == 'INFO':
self.stream.write('\033[0;33;40m')
elif function.__name__.upper() == 'DEBUG':
self.stream.write('\033[0;37;40m')
else:
self.stream.write('\033[0;37;40m')
function(self, string)
self.stream.write('\033[0m')
return real_func FORMAT = '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s' class Logger(object):
DEBUG_MODE = True
LOG_LEVEL = 5
GLOBAL_FILENAME = 'static/testlog/syslog/atc.log' def __init__(self, name, filename=None):
current_path = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'static', 'testlog/syslog')
if not os.path.exists(current_path):
os.makedirs(current_path) # baseconfig
logging.basicConfig()
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(FORMAT) # output to terminal
sh = logging.StreamHandler()
sh.setFormatter(formatter)
sh.setLevel(logging.DEBUG if self.DEBUG_MODE else logging.INFO)
self.logger.addHandler(sh)
self.stream = sh.stream # output to global file
if self.GLOBAL_FILENAME:
fh_all = logging.FileHandler(self.GLOBAL_FILENAME, 'a')
#fh_all = logging.handlers.TimedRotatingFileHandler(self.GLOBAL_FILENAME,'M',1,0)
#fh_all.suffix ="_%Y_%m_%d-%H_%M.log"
fh_all.setFormatter(formatter)
fh_all.setLevel(logging.DEBUG)
self.logger.addHandler(fh_all)
self.logger.propagate = 0 # output to user define file
if filename is not None:
fh = logging.FileHandler(filename, 'a')
fh.setFormatter(formatter)
fh.setLevel(logging.DEBUG)
self.logger.addHandler(fh)
self.logger.propagate = 0 @color_text_decorator
def hint(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 5:
return self.logger.debug(strTmp)
else:
pass @color_text_decorator
def debug(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 4:
return self.logger.debug(strTmp)
else:
pass @color_text_decorator
def info(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 3:
return self.logger.info(strTmp)
else:
pass @color_text_decorator
def warn(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 2:
return self.logger.warn(strTmp)
else:
pass @color_text_decorator
def error(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 1:
return self.logger.error(strTmp)
else:
pass class TestLogModule(object): def __init__(self):
pass def runtest(self):
logger = Logger('TEST') iCount = 0
while True:
iCount = iCount + 1
logger.error(str(iCount))
logger.debug('1 22 333 4444 55555 666666')
logger.info('1 22 333 4444 55555 666666')
logger.warn('1 22 333 4444 55555 666666')
logger.error('1 22 333 4444 55555 666666')
time.sleep(1)
if iCount >= 120:
break
# for a in xrange(10):
# logger.debug('1 22 333 4444 55555 666666')
# logger.info('1 22 333 4444 55555 666666')
# logger.warn('1 22 333 4444 55555 666666')
# logger.error('1 22 333 4444 55555 666666')
# time.sleep(1) if __name__ == '__main__':
TestLogModule().runtest()
我们的需求可以用logging.handlers实现,具体方法为logging.handlers.TimedRotatingFileHandler和logging.handlers.RotatingFileHandler。
class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)
返回RotatingFileHandler类的实例。指明的文件被打开,并被用作日志流。如果没有指明mode,使用'a'。如果encoding不为None,使用指定的编码来打开文件。如果delay为真,只到第一次调用emit()的时候才打开文件。默认情况下,文件会一直增长。 可以使用maxBytes 和 backupCount 来让文件在预定义的尺寸发生翻转。当文件大小大概要超出时,文件被关闭,新文件被打开用来输出。当文件大小接近于maxBytes长度时,翻转会发生;如果maxBytes为0,翻转永不发生。如果backupCount不为0,系统将保存老的日志文件,在文件名后加上‘.1’, ‘.2’这样的扩展名。例如如果backupCount是5,基本的文件名是app.log,将会得到app.log, app.log.1, app.log.2到 app.log.5。总是写到文件app.log中。当文件被填满,文件被关闭并重命名为app.log.1,而已存的app.log.1, app.log.2等文件被重命名为app.log.2, app.log.3等。 改变于版本2.6:新增了delay。 doRollover()
如上所述做文件的翻转。 emit(record)
输出记录到文件,负责文件的翻转。
class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False)
返回TimedRotatingFileHandler类的实例。 指明的文件被打开,并用作日志流。在循环时它也会设置文件后缀。循环发生基于when 和 interval的乘积。 使用when来指明interval的类型。可能的值列在下面。注意大小写不敏感。 Value Type of interval
'S' Seconds
'M' Minutes
'H' Hours
'D' Days
'W0'-'W6' Weekday (0=Monday)
'midnight' Roll over at midnight
注意在使用基于工作日的循环时,‘W0’表示星期一,‘W1’表示星期二,依此类推,‘W6’表示星期日。这种情况下不使用interval。 系统会保存老的日志文件,在文件名后添加扩展名。扩展名基于日期和时间,根据翻转间隔,使用strftime格式%Y-%m-%d_%H-%M-%S,或者其前面一部分。 第一次计算下一次翻转时间的时候(创建handler时),要么使用已存文件的上一次修改时间,要么使用当前时间。 如果utc为真,使用UTC时间;否则使用本地时间。 如果backupCount不为0,最多保留backupCount个文件,如果产生更多的文件,最老的文件会被删除。删除逻辑使用间隔来决定删除哪些文件,所以改变间隔可能会导致老的文件被保留。 如果delay为真,只到第一次调用emit()时文件才被打开。 改变于版本2.6:新增了delay和utc。 doRollover()
如上所述做文件的翻转。 emit(record)
输出记录到文件,负责文件的翻转。
改良第一步:将logging.handlers.TimedRotatingFileHandler和logging.handlers.RotatingFileHandler添加到初始版本中去
Logger类新增logginghandlers.TimeRotatingFileHandler和logging.handlers.RotatingFileHandler 的handler
class Logger(object):
DEBUG_MODE = True
LOG_LEVEL = 5
GLOBAL_FILENAME = 'static/testlog/syslog/atc.log' def __init__(self, name, filename=None):
current_path = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'static', 'testlog/syslog')
if not os.path.exists(current_path):
os.makedirs(current_path) # baseconfig
logging.basicConfig()
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(FORMAT) # output to terminal
sh = logging.StreamHandler()
sh.setFormatter(formatter)
sh.setLevel(logging.DEBUG if self.DEBUG_MODE else logging.INFO)
self.logger.addHandler(sh)
self.stream = sh.stream # output to global file
if self.GLOBAL_FILENAME:
th_all = logging.handlers.TimedRotatingFileHandler(self.GLOBAL_FILENAME, when='midnight',interval=1, backupCount=7)
th_all.setFormatter(formatter)
th_all.setLevel(logging.DEBUG)
self.logger.addHandler(th_all)
# self.logger.propagate = 0 rh_all = logging.handlers.RotatingFileHandler('static/testlog/syslog/rf.log', mode='a',maxBytes=2000*2000, backupCount=3)
rh_all.setFormatter(formatter)
rh_all.setLevel(logging.DEBUG)
self.logger.addHandler(rh_all)
# self.logger.propagate = 0 # output to user define file
if filename is not None:
fh = logging.FileHandler(filename, 'a')
fh.setFormatter(formatter)
fh.setLevel(logging.DEBUG)
self.logger.addHandler(fh)
self.logger.propagate = 0
实际结果并不如人意,新添加的两个handler在日志发生反转的时候,新建立的日志文件并不能把各模块的日志输出记录下来,会出现只记录了一部分模块日志的情况。
再次修改,这次我们为了移除模块共写一个文件的影响,另外新建一个mainlogger,由它记录各模块的日志输出,并且在日志反转时,新建日志文件。
#!/usr/bin/python
# -*- coding: utf-8 -*- import os
import time
import logging
import logging.handlers
import platform if platform.system() == 'Windows':
from ctypes import windll, c_ulong def color_text_decorator(function):
def real_func(self, string):
windll.Kernel32.GetStdHandle.restype = c_ulong
h = windll.Kernel32.GetStdHandle(c_ulong(0xfffffff5))
if function.__name__.upper() == 'ERROR':
windll.Kernel32.SetConsoleTextAttribute(h, 12)
elif function.__name__.upper() == 'WARN':
windll.Kernel32.SetConsoleTextAttribute(h, 13)
elif function.__name__.upper() == 'INFO':
windll.Kernel32.SetConsoleTextAttribute(h, 14)
elif function.__name__.upper() == 'DEBUG':
windll.Kernel32.SetConsoleTextAttribute(h, 15)
else:
windll.Kernel32.SetConsoleTextAttribute(h, 15)
function(self, string)
windll.Kernel32.SetConsoleTextAttribute(h, 15)
return real_func
else:
def color_text_decorator(function):
def real_func(self, string):
if function.__name__.upper() == 'ERROR':
self.stream.write('\033[0;31;40m')
elif function.__name__.upper() == 'WARN':
self.stream.write('\033[0;35;40m')
elif function.__name__.upper() == 'INFO':
self.stream.write('\033[0;33;40m')
elif function.__name__.upper() == 'DEBUG':
self.stream.write('\033[0;37;40m')
else:
self.stream.write('\033[0;37;40m')
function(self, string)
self.stream.write('\033[0m')
return real_func FORMAT = '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s' class MainLogger(object):
DEBUG_MODE = True
LOG_LEVEL = 5 def __init__(self, name):
current_path = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'static', 'testlog', 'syslog')
if not os.path.exists(current_path):
os.makedirs(current_path) # baseconfig
logging.basicConfig()
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(FORMAT) th_all = logging.handlers.TimedRotatingFileHandler(
os.path.join(current_path, 'master_main_atc.log'), when='midnight', interval=1, backupCount=7)
th_all.setFormatter(formatter)
th_all.setLevel(logging.DEBUG)
self.logger.addHandler(th_all) rh_all = logging.handlers.RotatingFileHandler(
os.path.join(current_path, 'master_main_logger_rf.log'), mode='a', maxBytes=2000 * 2000, backupCount=3)
rh_all.setFormatter(formatter)
rh_all.setLevel(logging.DEBUG)
self.logger.addHandler(rh_all)
# 防止在终端重复打印
self.logger.propagate = 0 def hint(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 5:
return self.logger.debug(strTmp)
else:
pass def debug(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 4:
return self.logger.debug(strTmp)
else:
pass def info(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 3:
return self.logger.info(strTmp)
else:
pass def warn(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 2:
return self.logger.warn(strTmp)
else:
pass def error(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
if self.LOG_LEVEL >= 1:
return self.logger.error(strTmp)
else:
pass main_logger = MainLogger('MasterMainLogger') class Logger(object):
DEBUG_MODE = True
LOG_LEVEL = 5 def __init__(self, name, filename=None): self.name = name
# baseconfig
logging.basicConfig()
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(FORMAT) # output to terminal
sh = logging.StreamHandler()
sh.setFormatter(formatter)
sh.setLevel(logging.DEBUG if self.DEBUG_MODE else logging.INFO)
self.logger.addHandler(sh)
self.stream = sh.stream # output to user define file
if filename is not None:
fh = logging.FileHandler(filename, 'a')
fh.setFormatter(formatter)
fh.setLevel(logging.DEBUG)
self.logger.addHandler(fh)
self.logger.propagate = 0 # 防止在终端重复打印
self.logger.propagate = 0 @color_text_decorator
def hint(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
main_logger.hint("[" + self.name + "] " + strTmp)
if self.LOG_LEVEL >= 5:
return self.logger.debug(strTmp)
else:
pass @color_text_decorator
def debug(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
main_logger.debug("[" + self.name + "] " + strTmp)
if self.LOG_LEVEL >= 4:
return self.logger.debug(strTmp)
else:
pass @color_text_decorator
def info(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
main_logger.info("[" + self.name + "] " + strTmp)
if self.LOG_LEVEL >= 3:
return self.logger.info(strTmp)
else:
pass @color_text_decorator
def warn(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
main_logger.warn("[" + self.name + "] " + strTmp)
if self.LOG_LEVEL >= 2:
return self.logger.warn(strTmp)
else:
pass @color_text_decorator
def error(self, string):
# 去除多余连续空格
strTmp = str(string)
strTmp = ' '.join(strTmp.split())
main_logger.error("[" + self.name + "] " + strTmp)
if self.LOG_LEVEL >= 1:
return self.logger.error(strTmp)
else:
pass class TestLogModule(object): def __init__(self):
pass def runtest(self):
logger = Logger('TEST') iCount = 0
while True:
iCount = iCount + 1
logger.error(str(iCount))
logger.debug('1 22 333 4444 55555 666666')
logger.info('1 22 333 4444 55555 666666')
logger.warn('1 22 333 4444 55555 666666')
logger.error('1 22 333 4444 55555 666666')
time.sleep(1)
if iCount >= 120:
break
# for a in xrange(10):
# logger.debug('1 22 333 4444 55555 666666')
# logger.info('1 22 333 4444 55555 666666')
# logger.warn('1 22 333 4444 55555 666666')
# logger.error('1 22 333 4444 55555 666666')
# time.sleep(1) if __name__ == '__main__':
TestLogModule().runtest()
这次,终于达到笔者现阶段的需求,但是又发现了新的问题,如果以上代码运行在多进程环境中,日志反转时,不再建立新日志文件,python的logging模块报错,提示文件已打开,导致日志记录失败。
python 跨模块实现按照文件大小,日期实现日志分割,反转的更多相关文章
- nginx实现按日期进行日志分割
1:nginx的访问日志按日期分割,也就是每天的零点把前一天的访问日志以日期的形式备份,然后重新打开一份访问日志,这里的kill -USR1 $pid 重新打开访问日志,必须得把原来的mv,如果存在的 ...
- Python 3 实现定义跨模块的全局变量和使用
尽管某些书籍上总是说避免使用全局变量,但是在实际的需求不断变化中,往往定义一个全局变量是最可靠的方法,但是又必须要避免变量名覆盖. Python 中 global 关键字可以定义一个变量为全局变量,但 ...
- Day05 - Python 常用模块
1. 模块简介 模块就是一个保存了 Python 代码的文件.模块能定义函数,类和变量.模块里也能包含可执行的代码. 模块也是 Python 对象,具有随机的名字属性用来绑定或引用. 下例是个简单的模 ...
- python logging模块详解[转]
一.简单将日志打印到屏幕: import logging logging.debug('debug message') logging.info('info message') logging.war ...
- Python logging模块详解
简单将日志打印到屏幕: import logging logging.debug('debug message') logging.info('info message') logging.warni ...
- python 各模块
01 关于本书 02 代码约定 03 关于例子 04 如何联系我们 1 核心模块 11 介绍 111 内建函数和异常 112 操作系统接口模块 113 类型支持模块 114 正则表达式 115 语言支 ...
- Day5 模块及Python常用模块
模块概述 定义:模块,用一砣代码实现了某类功能的代码集合. 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,提供了代码的重用性.在Python中,一个.py文件就称之为一个模块(Mod ...
- Day6 模块及Python常用模块
模块概述 定义:模块,用一砣代码实现了某类功能的代码集合. 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,提供了代码的重用性.在Python中,一个.py文件就称之为一个模块(Mod ...
- 04: python常用模块
目录: 1.1 时间模块time() 与 datetime() 1.2 random()模块 1.3 os模块 1.4 sys模块 1.5 tarfile用于将文件夹归档成 .tar的文件 1.6 s ...
随机推荐
- spring启动component-scan类扫描加载过程---源码分析
http://blog.csdn.net/xieyuooo/article/details/9089441#comments
- java简单日历
一.使用java的calendar类写一个简单的日历 package com.calendar; import java.util.Calendar; import java.util.Date; i ...
- Android_Activity生命周期
通过前面一段时间的学习,我们很清楚我们的一系列操作都离不开的一个东西,就是我们的activity .接下来我们对 Activity 进行系统的总结. Activity 的四种基本状态 1.运行态(Ru ...
- oracle+ibatis 批量插入-支持序列自增
首先请先看我前面一篇帖子了解oracle批量插入的sql:[oracle 批量插入-支持序列自增] 我用的ibatis2.0,sqlMap文件引入的标签如下: <!DOCTYPE sqlMap ...
- [原创.数据可视化系列之二]使用cesium三维地图展示美国全球军事基地分布
基于浏览器的三维地图还算是一个比较高冷的东西,最主要的技术难点是如何在浏览器上 多快好省 的显示三维数据,很遗憾,还真的没有太好的的方案,只能说还有可行的方案. 很久之前用过skyline,使用CS居 ...
- perl中常见的语法规则和函数
数值比较操作符 字符串 相等 == eq 不等 != ...
- html5的新标签
header,section,footer,aside,nav,main,article,figure 在使用的时候,需要先把这个元素变为块级元素,确保在老的浏览器里面可以使用.因为在浏览器检测到未知 ...
- 提高sevenzipsharp 检查密码的速度(1)
前言:sevenzipsharp检查密码(包括检查压缩包的有效性)的函数是SevenZipExtractor.check(), sevenzipsharp调用的是7zip的动态链接库,而且不止是7zi ...
- Git相关知识
一些有用的链接: https://www.git-scm.com/ http://nvie.com/posts/a-successful-git-branching-model/ Git开发模式: 建 ...
- Git使用心得
1.git是分布式的版本控制(有本地仓库)git 先对安全 2.git基于元数据 svn是基于目录的 3.本地的提交分为三个步骤(提交本地仓库先提交暂存区再提交本地仓库) 工作区 ...