【大爆炸】log 模块
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
File Name: hcs-log
Description : log 模块
Author : jiaoyaxiong
date: 2019/7/22
-------------------------------------------------
Change Activity:
2019/7/22:
-------------------------------------------------
"""
__author__ = 'jiaoyaxiong'
import logging
import time
import os
import yaml
import conf
import functools
"""
从环境变量中获取如下信息:
jenkins 启动时注入:
testEnv = daily|pre|online
jenBuild =
jenJobName =
needUpdate = yes|no
从代码路径获取信息:
codeCID =
codeBName =
需要大的组件:
jenkins (docker 启动时开启jenkins )
git (需要内网访问支持)
python
...
log 存放:---亦可以上传至sls(待验证是否支持通配符模式)
本地需要存放一份----allure 附件上传的方式推送到 allure report
每个用例跑的时候生成一份,附送到allure
"""
"""
在jenkins 构建脚本中注入环境变量
#注入环境变量
export testEnv=daily
export jenBuild=${BUILD_ID}
export jenJobName=${JOB_NAME}
export needUpdate=no
jenkins 启动测试进程可以获取到环境变量 os.environ.get()
logging 日志可以传入多余的格式
file_formatter=logging.Formatter('[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(testEnv)s][%(jenBuild)s][%(jenJobName)s][%(codeCID)s][%(codeBName)s][%(casename)s][%(message)s]')
def getEnv():
testEnv=os.environ.get("testEnv","null-testEnv")
jenBuild=os.environ.get("jenBuild","null-jenBuild")
jenJobName=os.environ.get("jenJobName","null-jenJobName")
needUpdate=os.environ.get("needUpdate","no")
codeCID=os.environ.get("codeCID","null-codeCID")
codeBName=os.environ.get("codeBName","null-codeBName")
return {"testEnv":testEnv,
"jenBuild":jenBuild,
"jenJobName":jenJobName,
"needUpdate":needUpdate,
"codeCID":codeCID,
"codeBName":codeBName
}
self.logger.info=functools.partial(self.logger.info,extra=results_env)
"""
def getEnv():
testEnv=os.environ.get("testEnv","null-testEnv")
jenBuild=os.environ.get("jenBuild","null-jenBuild")
jenJobName=os.environ.get("jenJobName","null-jenJobName")
needUpdate=os.environ.get("needUpdate","no")
codeCID=os.environ.get("codeCID","null-codeCID")
codeBName=os.environ.get("codeBName","null-codeBName")
return {"testEnv":testEnv,
"jenBuild":jenBuild,
"jenJobName":jenJobName,
"needUpdate":needUpdate,
"codeCID":codeCID,
"codeBName":codeBName
}
# os.environ 设置的是临时环境变量,只能在一次运行中使用
# 这个环境变量只在进程和子进程中可以获取到
# 这里所谓的修改环境变量,仅仅影响当前python进程后续启动的子进程,对当前python进程以外的环境是没有任何影响的。
# https://www.zhihu.com/question/55937152
# linux source
# source命令(从 C Shell 而来)是bash shell的内置命令. 点命令,就是一个点符号,是source的另一名称。这两个命令都以一个脚本为参数,
# 该脚本将在当前shell的环境执行,即不会启动一个新的子shell。所有在脚本中设置的变量都将成为当前Shell的一部分。
# 不会启动子shell, 在source filename 与 sh filename 及. / filename执行脚本的区别 #
# 当shell脚本具有可执行权限时,用sh
# filename与. / filename执行脚本是没有区别得。./ filename是因为当前目录没有在PATH中,所有”.”是用来表示当前目录的。
# sh
# filename
# 重新建立一个子shell,在子shell中执行脚本里面的语句,该子shell继承父shell的环境变量,但子shell新建的、改变的变量不会被带回父shell,除非使用export。
# source
# filename:这个命令其实只是简单地读取脚本里面的语句依次在当前shell里面执行,没有建立新的子shell。那么脚本里面所有新建、改变变量的语句都会保存在当前shell里面。
# 在shell中执行程序时,shell会提供一组环境变量。 export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该此登陆操作
# 一个变量创建时,它不会自动地为在它之后创建的shell进程所知。而命令export可以向后面的shell传递变量的值。当一个shell脚本调用并执行时,它不会自动得到原为脚本(调用者)里定义的变量的访问权,除非这些变量已经被显式地设置为可用。export命令可以用于传递一个或多个变量的值到任何后继脚本。
# 要想永久生效,需要把这行添加到环境变量文件里。有两个文件可选:“/etc/profile”和用户主目录下的“.bash_profile”,“/etc/profile”对系统里所有用户都有效
# 登陆系统才会执行/etc/profile (login 初始化环境变量等 登陆shell 非登陆shell ),不登陆系统就会执行 /etc/rc.d/rc.local (开机自动启动)
# https://www.cnblogs.com/syavingcs/p/14058134.html
# 设置环境变量的脚本,可以放在profile.d目录下面,但开机执行任务不应该放在profile.d目录下,因为每次登陆都会执行profile.d目录下的文件,会导致重复执行,
# 设置开机启动任务,应该放在rc.local中执行,它只会在系统启动时执行一次。
# Linux登录shell和非登录(交互式shell)环境变量配置 https://www.cnblogs.com/woshimrf/p/shell-conf.html
# 单用户模式进行救援
# subprocess multiprocessing
# subprocess is 用来执行其他的可执行程序的,即执行外部命令。 他是os.fork() 和 os.execve() 的封装。 他启动的进程不会把父进程的模块加载一遍。使用subprocess的通信机制比较少,通过管道或者信号机制.
# subprocess.run 功能,执行args参数所表示的命令,等待命令结束,返回一个CompletedProcess类型对象
# subprocess.Popen() 用法和参数run()方法基本相同,但是他的返回值是一个Popen对象,而不是CompletedProcess对象 异步执行
# multiprocessing 用来执行python的函数,他启动的进程会重新加载父进程的代码。可以通过Queue、Array、Value等对象来通信。
# from multiprocessing import Process p1 = Process(target=run_proc1,args=(‘test’,))
# 1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
class hcs_logger():
def __init__(self,casename):
base_log_path=conf.get_conf()["global"]["base_log_path"]
self.logger = logging.getLogger(casename)
self.logger.setLevel(level = logging.INFO)
file_formate=base_log_path+os.sep+time.strftime('%Y%m%d',time.localtime(time.time()))
try:
if not os.path.exists(file_formate):
os.makedirs(file_formate)
except:
time.sleep(1)
pass
timestamp=time.strftime('%H%M%S',time.localtime(time.time()))
handler = logging.FileHandler("%s//%s%s.txt"%(file_formate,str(casename),timestamp),encoding="utf-8")
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s')
#写复杂点,给sls 分析log 使用
file_formatter=logging.Formatter('[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(testEnv)s][%(jenBuild)s][%(jenJobName)s][%(codeCID)s][%(codeBName)s][%(casename)s][%(message)s]')
handler.setFormatter(file_formatter)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
console.setFormatter(formatter)
# 给上传allure 附件使用
self.txt_log_path="%s//%s%s.txt" % ( file_formate,str(casename),timestamp)
self.logger.addHandler(handler)
self.logger.addHandler(console)
results_env=getEnv()
results_env["casename"]=casename
#传点参数
"""
从名字可以看出,该函数的作用就是部分使用某个函数,即冻结住某个函数的某些参数,让它们保证为某个值,并生成一个可调用的新函数对象,这样你就能够直接调用该新对象,并且仅用使用很少的参数
functools.partial 偏函数的使用
多余参数:
import logging
FORMAT = '%(asctime)s %(clientip)s %(user)-8s %(message)s %(haiyoushui)s'
logging.basicConfig(format=FORMAT)
d={'clientip':'192.168.0.1','user':'fbloggs', "haiyoushui":"12312313113213"}
logger=logging.getLogger('tcpserver')
logger.warning('Protocolproblem:%s','connectionreset',extra=d)
extra是用户自定义的dict. 这些key/value在格式化的时候可以直接引用。
用Python的logging模块记录日志时,遇到了重复记录日志的问题,第一条记录写一次,第二条记录写两次,第三条记录写三次。。。很头疼,这样记日志可不行。网上搜索到了原因与解决方案:
原因:没有移除handler
解决:在日志记录完之后removeHandler
logger.removeHandler(streamhandler)
所以这里有以下几个解决办法:
每次创建不同name的logger,每次都是新logger,不会有添加多个handler的问题。(ps:这个办法太笨,不过我之前就是这么干的。。)
像上面一样每次记录完日志之后,调用removeHandler()把这个logger里的handler移除掉。
在log方法里做判断,如果这个logger已有handler,则不再添加handler。
与方法2一样,不过把用pop把logger的handler列表中的handler移除。
"""
self.logger.info=functools.partial(self.logger.info,extra=results_env)
self.logger.error=functools.partial(self.logger.error,extra=results_env)
self.logger.debug=functools.partial(self.logger.debug,extra=results_env)
self.logger.warning=functools.partial(self.logger.warning,extra=results_env)
def __call__(self, *args, **kwargs):
return self.logger
# __call__ 使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用
if __name__ == "__main__":
logging1=hcs_logger("casename123")()
# logging1.info("123",extra=getEnv())
logging1.info("123")
logging1.error("321")
logging1.debug("12553")
logging1.warning("124443")
# DEBUG 有问题!
logging1.debug("12553")
file_name = os.path.split(os.path.abspath(__file__))[-1]
logging1.info(file_name.replace(".py",""))
# -*- coding: utf-8 -*-"""------------------------------------------------- File Name: hcs-log Description : log 模块 Author : wb-jyx515728 date: 2019/7/22------------------------------------------------- Change Activity: 2019/7/22:-------------------------------------------------"""__author__ = 'wb-jyx515728'
import loggingimport timeimport osimport yamlimport confimport functools
"""从环境变量中获取如下信息:
jenkins 启动时注入:testEnv = daily|pre|onlinejenBuild =jenJobName =needUpdate = yes|no
从代码路径获取信息:codeCID =codeBName =
需要大的组件:jenkins (docker 启动时开启jenkins )git (需要内网访问支持)python...
log 存放:---亦可以上传至sls(待验证是否支持通配符模式)本地需要存放一份----allure 附件上传的方式推送到 allure report 每个用例跑的时候生成一份,附送到allure
"""
"""在jenkins 构建脚本中注入环境变量#注入环境变量export testEnv=dailyexport jenBuild=${BUILD_ID}export jenJobName=${JOB_NAME}export needUpdate=no
jenkins 启动测试进程可以获取到环境变量 os.environ.get()logging 日志可以传入多余的格式 file_formatter=logging.Formatter('[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(testEnv)s][%(jenBuild)s][%(jenJobName)s][%(codeCID)s][%(codeBName)s][%(casename)s][%(message)s]')
def getEnv(): testEnv=os.environ.get("testEnv","null-testEnv") jenBuild=os.environ.get("jenBuild","null-jenBuild") jenJobName=os.environ.get("jenJobName","null-jenJobName") needUpdate=os.environ.get("needUpdate","no") codeCID=os.environ.get("codeCID","null-codeCID") codeBName=os.environ.get("codeBName","null-codeBName") return {"testEnv":testEnv, "jenBuild":jenBuild, "jenJobName":jenJobName, "needUpdate":needUpdate, "codeCID":codeCID, "codeBName":codeBName }
self.logger.info=functools.partial(self.logger.info,extra=results_env)
"""
def getEnv(): testEnv=os.environ.get("testEnv","null-testEnv") jenBuild=os.environ.get("jenBuild","null-jenBuild") jenJobName=os.environ.get("jenJobName","null-jenJobName") needUpdate=os.environ.get("needUpdate","no") codeCID=os.environ.get("codeCID","null-codeCID") codeBName=os.environ.get("codeBName","null-codeBName") return {"testEnv":testEnv, "jenBuild":jenBuild, "jenJobName":jenJobName, "needUpdate":needUpdate, "codeCID":codeCID, "codeBName":codeBName }
# os.environ 设置的是临时环境变量,只能在一次运行中使用# 这个环境变量只在进程和子进程中可以获取到# 这里所谓的修改环境变量,仅仅影响当前python进程后续启动的子进程,对当前python进程以外的环境是没有任何影响的。# https://www.zhihu.com/question/55937152
# linux source
# source命令(从 C Shell 而来)是bash shell的内置命令. 点命令,就是一个点符号,是source的另一名称。这两个命令都以一个脚本为参数, # 该脚本将在当前shell的环境执行,即不会启动一个新的子shell。所有在脚本中设置的变量都将成为当前Shell的一部分。 # 不会启动子shell, 在source filename 与 sh filename 及. / filename执行脚本的区别 # # 当shell脚本具有可执行权限时,用sh # filename与. / filename执行脚本是没有区别得。./ filename是因为当前目录没有在PATH中,所有”.”是用来表示当前目录的。 # sh # filename # 重新建立一个子shell,在子shell中执行脚本里面的语句,该子shell继承父shell的环境变量,但子shell新建的、改变的变量不会被带回父shell,除非使用export。 # source # filename:这个命令其实只是简单地读取脚本里面的语句依次在当前shell里面执行,没有建立新的子shell。那么脚本里面所有新建、改变变量的语句都会保存在当前shell里面。
# 在shell中执行程序时,shell会提供一组环境变量。 export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该此登陆操作
# 一个变量创建时,它不会自动地为在它之后创建的shell进程所知。而命令export可以向后面的shell传递变量的值。当一个shell脚本调用并执行时,它不会自动得到原为脚本(调用者)里定义的变量的访问权,除非这些变量已经被显式地设置为可用。export命令可以用于传递一个或多个变量的值到任何后继脚本。
# 要想永久生效,需要把这行添加到环境变量文件里。有两个文件可选:“/etc/profile”和用户主目录下的“.bash_profile”,“/etc/profile”对系统里所有用户都有效
# 登陆系统才会执行/etc/profile (login 初始化环境变量等 登陆shell 非登陆shell ),不登陆系统就会执行 /etc/rc.d/rc.local (开机自动启动)
# https://www.cnblogs.com/syavingcs/p/14058134.html # 设置环境变量的脚本,可以放在profile.d目录下面,但开机执行任务不应该放在profile.d目录下,因为每次登陆都会执行profile.d目录下的文件,会导致重复执行, # 设置开机启动任务,应该放在rc.local中执行,它只会在系统启动时执行一次。 # Linux登录shell和非登录(交互式shell)环境变量配置 https://www.cnblogs.com/woshimrf/p/shell-conf.html
# 单用户模式进行救援
# subprocess multiprocessing # subprocess is 用来执行其他的可执行程序的,即执行外部命令。 他是os.fork() 和 os.execve() 的封装。 他启动的进程不会把父进程的模块加载一遍。使用subprocess的通信机制比较少,通过管道或者信号机制.
# subprocess.run 功能,执行args参数所表示的命令,等待命令结束,返回一个CompletedProcess类型对象 # subprocess.Popen() 用法和参数run()方法基本相同,但是他的返回值是一个Popen对象,而不是CompletedProcess对象 异步执行 # multiprocessing 用来执行python的函数,他启动的进程会重新加载父进程的代码。可以通过Queue、Array、Value等对象来通信。 # from multiprocessing import Process p1 = Process(target=run_proc1,args=(‘test’,))
# 1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
class hcs_logger(): def __init__(self,casename): base_log_path=conf.get_conf()["global"]["base_log_path"] self.logger = logging.getLogger(casename) self.logger.setLevel(level = logging.INFO) file_formate=base_log_path+os.sep+time.strftime('%Y%m%d',time.localtime(time.time())) try: if not os.path.exists(file_formate): os.makedirs(file_formate) except: time.sleep(1) pass timestamp=time.strftime('%H%M%S',time.localtime(time.time())) handler = logging.FileHandler("%s//%s%s.txt"%(file_formate,str(casename),timestamp),encoding="utf-8") handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s') #写复杂点,给sls 分析log 使用 file_formatter=logging.Formatter('[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(testEnv)s][%(jenBuild)s][%(jenJobName)s][%(codeCID)s][%(codeBName)s][%(casename)s][%(message)s]')
handler.setFormatter(file_formatter)
console = logging.StreamHandler() console.setLevel(logging.DEBUG) console.setFormatter(formatter) # 给上传allure 附件使用 self.txt_log_path="%s//%s%s.txt" % ( file_formate,str(casename),timestamp) self.logger.addHandler(handler) self.logger.addHandler(console) results_env=getEnv() results_env["casename"]=casename #传点参数 """ 从名字可以看出,该函数的作用就是部分使用某个函数,即冻结住某个函数的某些参数,让它们保证为某个值,并生成一个可调用的新函数对象,这样你就能够直接调用该新对象,并且仅用使用很少的参数 functools.partial 偏函数的使用 多余参数: import logging
FORMAT = '%(asctime)s %(clientip)s %(user)-8s %(message)s %(haiyoushui)s' logging.basicConfig(format=FORMAT) d={'clientip':'192.168.0.1','user':'fbloggs', "haiyoushui":"12312313113213"} logger=logging.getLogger('tcpserver') logger.warning('Protocolproblem:%s','connectionreset',extra=d) extra是用户自定义的dict. 这些key/value在格式化的时候可以直接引用。
用Python的logging模块记录日志时,遇到了重复记录日志的问题,第一条记录写一次,第二条记录写两次,第三条记录写三次。。。很头疼,这样记日志可不行。网上搜索到了原因与解决方案: 原因:没有移除handler 解决:在日志记录完之后removeHandler logger.removeHandler(streamhandler)
所以这里有以下几个解决办法:
每次创建不同name的logger,每次都是新logger,不会有添加多个handler的问题。(ps:这个办法太笨,不过我之前就是这么干的。。) 像上面一样每次记录完日志之后,调用removeHandler()把这个logger里的handler移除掉。 在log方法里做判断,如果这个logger已有handler,则不再添加handler。 与方法2一样,不过把用pop把logger的handler列表中的handler移除。 """
self.logger.info=functools.partial(self.logger.info,extra=results_env) self.logger.error=functools.partial(self.logger.error,extra=results_env) self.logger.debug=functools.partial(self.logger.debug,extra=results_env) self.logger.warning=functools.partial(self.logger.warning,extra=results_env)
def __call__(self, *args, **kwargs): return self.logger
# __call__ 使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用
if __name__ == "__main__": logging1=hcs_logger("casename123")() # logging1.info("123",extra=getEnv()) logging1.info("123") logging1.error("321") logging1.debug("12553") logging1.warning("124443") # DEBUG 有问题! logging1.debug("12553") file_name = os.path.split(os.path.abspath(__file__))[-1] logging1.info(file_name.replace(".py",""))
浙公网安备 33010602011771号