【大爆炸】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",""))