1. hashlib
hashlib的作用:用于确认文件是否被更改,也可以用来密文存储密码等
特点:
1. hash加密之后长度一样
2. hash加密的东西一样,则哈希的值是一样的。
3. 如果使用同一个hash字符编码表,那么它的结果会不断的累加
import hashlib
hash_obj = hashlib.md5()
strings = b"panlifu"
hash_obj.update(strings)
# panlifu 6099a053b1a5fd9c87fe34d68d6cc829
print(hash_obj.hexdigest())
str_a = b"lt"
hash_obj.update(str_a)
# panlifult c64625db271a6b9c5082fca53722e555
print(hash_obj.hexdigest())
可以用来存储用户密文密码:
#用户的登录
import hashlib
usr = input('username :')
pwd = input('password : ')
with open('userinfo') as f:
for line in f:
user,passwd,role = line.split('|')
md5 = hashlib.md5()
md5.update(bytes(pwd,encoding='utf-8'))
md5_pwd = md5.hexdigest()
if usr == user and md5_pwd == passwd:
print('登录成功')
2. hmac
hmace的作用:跟hashlib用法相同,用于确认文件是否被更改,同时也可以用来密文存储密码
#加盐
import hashlib # 提供摘要算法的模块
md5 = hashlib.md5(bytes('盐',encoding='utf-8')) #加盐的步骤
# md5 = hashlib.md5()
md5.update(b'123456')
print(md5.hexdigest())
使用用户名的一部分或者 直接使用整个用户名作为盐
import hashlib # 提供摘要算法的模块
md5 = hashlib.md5(bytes('盐',encoding='utf-8')+b'这里为用户的信息')
# md5 = hashlib.md5()
md5.update(b'123456')
print(md5.hexdigest())
3. UUID
1. UUID是什么:
通用唯一标识符 ( Universally Unique Identifier ), 对于所有的UUID它可以保证在空间和时间上的唯一性. 它是通过MAC地址, 时间戳, 命名空间, 随机数, 伪随机数来保证生成ID的唯一性, 有着固定的大小( 128 bit )
2. UUID特点:
它的唯一性和一致性特点使得可以无需注册过程就能够产生一个新的UUID. UUID可以被用作多种用途, 既可以用来短时间内标记一个对象, 也可以可靠的辨别网络中的持久性对象.
3. 为什么使用UUID?
很多应用场景需要一个id, 但是又不要求这个id 有具体的意义, 仅仅用来标识一个对象. 常见的例子有数据库表的id 字段. 另一个例子是前端的各种UI库, 因为它们通常需要动态创建各种UI元素, 这些元素需要唯一的id , 这时候就需要使用UUID了.
4. UUID的用法
**python的uuid模块提供UUID类和函数uuid1(), uuid3(), uuid4(), uuid5() 来生成1, 3, 4, 5各个版本的UUID ( 需要注意的是: python中没有uuid2()这个函数). 对uuid模块中最常用的几个函数总结如下: **
1. uuid.uuid1([node[, clock_seq]])
: 基于时间戳
**使用主机ID, 序列号, 和当前时间来生成UUID, 可保证全球范围的唯一性. 但由于使用该方法生成的UUID中包含有主机的网络地址, 因此可能危及隐私. 该函数有两个参数, 如果 node 参数未指定, 系统将会自动调用 getnode() 函数来获取主机的硬件地址. 如果 clock_seq 参数未指定系统会使用一个随机产生的14位序列号来代替. **
uuid.uuid3(namespace, name)
: 基于名字的MD5散列值
通过计算命名空间和名字的MD5散列值来生成UUID, 可以保证同一命名空间中不同名字的唯一性和不同命名空间的唯一性, 但同一命名空间的同一名字生成的UUID相同.
uuid.uuid4()
: 基于随机数
**通过随机数来生成UUID. 使用的是伪随机数有一定的重复概率. **
uuid.uuid5(namespace, name)
: 基于名字的SHA-1散列值
4. logging
logging的作用:记录程序运行期间的状态
V1版本(直接使用)
import logging
logging.debug('调试信息')
logging.info('正常信息')
logging.warning('警告信息')
logging.error('报错信息')
logging.critical('严重错误信息')
'''
WARNING:root:警告信息
ERROR:root:报错信息
CRITICAL:root:严重错误信息
'''
'''
总结:
1. V1的版本无法指定日志的级别;
2. 无法指定日志的格式;
3. 只能往屏幕打印,无法写入文件
'''
V2版本(半定制)
# V2版本
import logging
# 日志的基本配置
logging.basicConfig(filename='access.log',format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S %p',level=10)
logging.debug('调试信息') # 10
logging.info('正常信息') # 20
logging.warning('警告信息') # 30
logging.error('报错信息') # 40
logging.critical('严重错误信息') # 50
'''
总结:
1. 所有类别的文件都打印到了一个文件中,同时不能设置日志文件的大小等
1. V1和V2版本能使我们快速上手logger,但是并不能满足我们实际的使用,我们还需要自定义Logger
'''
V3版本(半自定义)
import logging
# 0 10 20 30 40 50
# notset-->debug --> info --> warning --> error --> critical
# 1. 设置logging对象
plf_log = logging.getLogger("ATM")
# 2. Filter对象:不常用,略
# 3. Handler对象:接受logger传来的日志,然后控制输出
info_log = logging.FileHandler("info.log") # 打印到文件
error_log = logging.FileHandler("error.log") # 打印到文件
pm = logging.StreamHandler() # 打印到终端
# 4. Formatter对象:日志格式
style1 = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(module)s : %(message)s',datefmt='%Y-%m-%d %H:%M:%S %p')
style2 = logging.Formatter('%(asctime)s:%(message)s',datefmt='%Y-%m-%d %H:%M:%S %p')
style3 = logging.Formatter('%(name)s %(message)s')
# 5. 为Handler对象绑定格式
info_log.setFormatter(style1)
error_log.setFormatter(style2)
pm.setFormatter(style3)
# 6. 将Handler添加给logger并设置日志级别
plf_log.addHandler(info_log)
plf_log.addHandler(error_log)
plf_log.addHandler(pm)
# 7. 设置日志级别,可以在两个关卡进行设置logger与handler
# plf_log是第一级过滤,然后才能到handler
plf_log.setLevel(9)
info_log.setLevel(8)
error_log.setLevel(8)
pm.setLevel(8)
plf_log.disabled = True
# 8. 测试
plf_log.debug('debug')
plf_log.info('info')
plf_log.warning('warning')
plf_log.error('error')
plf_log.critical('critical')
plf_log.disabled = False
plf_log.debug('debug')
plf_log.info('info')
Formatter对象设置具体的输出格式,常见变量整理
变量 | 格式 | 变量描述 |
---|---|---|
asctime | %(asctime)s | 将日志的时间构造成可读的形式,默认情况下是精确到毫秒,如 2018-10-13 23:24:57,832,可以额外指定 datefmt 参数来指定该变量的格式 |
name | %(name) | 日志对象的名称 |
filename | %(filename)s | 不包含路径的文件名 |
pathname | %(pathname)s | 包含路径的文件名 |
funcName | %(funcName)s | 日志记录所在的函数名 |
levelname | %(levelname)s | 日志的级别名称 |
message | %(message)s | 具体的日志信息 |
lineno | %(lineno)d | 日志记录所在的行号 |
pathname | %(pathname)s | 完整路径 |
process | %(process)d | 当前进程ID |
processName | %(processName)s | 当前进程名称 |
thread | %(thread)d | 当前线程ID |
threadName | %threadName)s | 当前线程名称 |
V3版本中,需要注意的几个小细节
-
Logger 对象和 Handler 对象都可以设置日志级别,那到底以哪个为准?
-
默认 Logger 对象级别为 30 ,也即 WARNING。
-
默认 Handler 对象级别为 0,也即 NOTSET。
-
日志先经过Logger过滤,然后在经过Handler刷选。什么意思了。直接上代码吧
import logging plf_logger = logging.getLogger("ATM") hander_one = logging.FileHandler("error.log") hander_two = logging.FileHandler("info.log") format_one = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(module)s : %(message)s',datefmt='%Y-%m-%d %H:%M:%S %p') format_two = logging.Formatter('%(asctime)s:%(message)s',datefmt='%Y-%m-%d %H:%M:%S %p') hander_one.setFormatter(format_one) hander_two.setFormatter(format_two) plf_logger.addHandler(hander_one) plf_logger.addHandler(hander_two) # 设置级别 plf_logger.setLevel(10) #logger对象设置级别为10 hander_one.setLevel(30) # handler对象设置级别为30 hander_two.setLevel(40) # 另一个handler对象设置为40 plf_logger.debug('debug') plf_logger.info('info') plf_logger.warning('warning') plf_logger.error('error') plf_logger.critical('critical')
# 以下为error.log日志的内容 2019-06-11 16:26:23 PM - ATM - WARNING - test : warning 2019-06-11 16:26:23 PM - ATM - ERROR - test : error 2019-06-11 16:26:23 PM - ATM - CRITICAL - test : critical
# 以下为info.log日志的内容 2019-06-11 16:26:23 PM:error 2019-06-11 16:26:23 PM:critical
-
-
中文乱码问题
-
自定义Logger配置,指定字符编码
handler = logging.FileHandler(filename="test.log", encoding="utf-8")
-
使用默认Logger配置,指定字符编码
logging.basicConfig(handlers=[logging.FileHandler("test.log", encoding="utf-8")], level=logging.DEBUG)
-
-
临时禁用日志输出
-
使用logging时,可设置不输出某一级别的日志
import logging logging.disable(logging.INFO)
-
使用自定义Logger对象时,直接设置所有日志不输出
logger.disabled = True
-
-
日志文件按照时间划分或者按照大小划分
# 第一种: # 每隔 1000 Byte 划分一个日志文件,备份文件为 3 个 file_handler = logging.handlers.RotatingFileHandler("test.log", mode="w", maxBytes=1000, backupCount=3, encoding="utf-8") # 第二种 # 每隔 1小时 划分一个日志文件,interval 是时间间隔,备份文件为 10 个 handler2 = logging.handlers.TimedRotatingFileHandler("test.log", when="H", interval=1, backupCount=10)
-
日志文件如何轮询?
#!/usr/bin/env python #_*_coding:utf-8_*_ # vim : set expandtab ts=4 sw=4 sts=4 tw=100 : import logging import time import re from logging.handlers import TimedRotatingFileHandler from logging.handlers import RotatingFileHandler def main(): #日志打印格式 log_fmt = '%(asctime)s File "%(filename)s",line %(lineno)s %(levelname)s: %(message)s' formatter = logging.Formatter(log_fmt) #创建TimedRotatingFileHandler对象 log_file_handler = TimedRotatingFileHandler(filename="ds_update", when="M", interval=2, backupCount=2) #log_file_handler.suffix = "%Y-%m-%d_%H-%M.log" #log_file_handler.extMatch = re.compile(r"^d{4}-d{2}-d{2}_d{2}-d{2}.log$") log_file_handler.setFormatter(formatter) logging.basicConfig(level=logging.INFO) log = logging.getLogger() log.addHandler(log_file_handler) #循环打印日志 log_content = "test log" count = 0 while count < 30: log.error(log_content) time.sleep(20) count = count + 1 log.removeHandler(log_file_handler) if __name__ == "__main__": main() ''' # 在TimedRotatingFileHandler方法中参数解释如下: 1. filename:日志文件名的prefix; 2. when:是一个字符串,用于描述滚动周期的基本单位,字符串的值及意义如下: “S”: Seconds “M”: Minutes “H”: Hours “D”: Days “W”: Week day (0=Monday) “midnight”: Roll over at midnight 3. interval: 滚动周期,单位有when指定,比如:when=’D’,interval=1,表示每天产生一个日志文件; 4. backupCount: 表示日志文件的保留个数; 除了上述参数之外,TimedRotatingFileHandler还有两个比较重要的成员变量,它们分别是suffix和extMatch。suffix是指日志文件名的后缀,suffix中通常带有格式化的时间字符串,filename和suffix由“.”连接构成文件名(例如:filename=“runtime”, suffix=“%Y-%m-%d.log”,生成的文件名为runtime.2015-07-06.log)。extMatch是一个编译好的正则表达式,用于匹配日志文件名的后缀,它必须和suffix是匹配的,如果suffix和extMatch匹配不上的话,过期的日志是不会被删除的。比如,suffix=“%Y-%m-%d.log”, extMatch的只应该是re.compile(r”^d{4}-d{2}-d{2}.log$”)。默认情况下,在TimedRotatingFileHandler对象初始化时,suffxi和extMatch会根据when的值进行初始化: ‘S’: suffix=”%Y-%m-%d_%H-%M-%S”, extMatch=r”^d{4}-d{2}-d{2}_d{2}-d{2}-d{2}”; ‘M’:suffix=”%Y-%m-%d_%H-%M”,extMatch=r”^d{4}-d{2}-d{2}_d{2}-d{2}”; ‘H’:suffix=”%Y-%m-%d_%H”,extMatch=r”^d{4}-d{2}-d{2}_d{2}”; ‘D’:suffxi=”%Y-%m-%d”,extMatch=r”^d{4}-d{2}-d{2}”; ‘MIDNIGHT’:”%Y-%m-%d”,extMatch=r”^d{4}-d{2}-d{2}”; ‘W’:”%Y-%m-%d”,extMatch=r”^d{4}-d{2}-d{2}”; 如果对日志文件名没有特殊要求的话,可以不用设置suffix和extMatch,如果需要,一定要让它们匹配上 ''' # 该示例主要参考的文献为:https://blog.csdn.net/ashi198866/article/details/46725813
终极版本(全自定义)
以上三个版本的日志只是为了引出我们下面的日志配置文件
import os
import logging.config
# 定义三种日志输出格式 开始
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]' # 其中name为getLogger()指定的名字;lineno为调用日志输出函数的语句所在的代码行
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'
# 定义日志输出格式 结束
# 定义日志路径 开始
logfile_dir = os.path.dirname(os.path.abspath(__file__)) # log文件的目录,需要自定义文件路径
logfile_name = 'all2.log' # log文件名,需要自定义路径名
# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):
os.mkdir(logfile_dir)
# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)
# 定义日志路径 结束
# log配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
},
'filters': {}, # filter可以不定义
'handlers': {
# 打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
# 打印到文件的日志,收集info及以上的日志
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'standard',
'filename': logfile_path, # 日志文件
'maxBytes': 1024*1024*5, # 日志大小 5M
'backupCount': 5, # 文件的备份数量为5个
'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
},
},
'loggers': {
# logging.getLogger(__name__)拿到的logger配置。如果''设置为固定值logger1,则下次导入必须设置成logging.getLogger('logger1')
'': {
# 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': False, # 向上(更高level的logger)传递
},
},
}
def load_my_logging_cfg():
logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置
logger = logging.getLogger(__name__) # 生成一个log实例
logger.info('It works!') # 记录该文件的运行状态
if __name__ == '__main__':
load_my_logging_cfg()
使用日志
import time
import logging
import my_logging # 导入自定义的logging配置
logger = logging.getLogger(__name__) # 生成logger实例
def demo():
logger.debug("start range... time:{}".format(time.time()))
logger.info("中文测试开始。。。")
for i in range(10):
logger.debug("i:{}".format(i))
time.sleep(0.2)
else:
logger.debug("over range... time:{}".format(time.time()))
logger.info("中文测试结束。。。")
if __name__ == "__main__":
my_logging.load_my_logging_cfg() # 在你程序文件的入口加载自定义logging配置
demo()
补充知识:
logging中的Filter:
作用:
1. Filter用来过滤日志信息,例如你想输出A类信息,但不想输出C类信息,就可以进行过滤
2. 而由于所有的信息都有经过过滤器,也可以使用过滤器来增加一些信息
import logging
class ContextFilter(logging.Filter): # 自定义类
def filter(self, record):
if record.role == "admin":
return True
else:
return False
if __name__ == '__main__':
logger = logging.getLogger("Wechat")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s Role: %(role)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# 创建绑定fiter
f = ContextFilter()
logger.addFilter(f)
logger.info('An info message with %s', 'some parameters',extra={"role":"admin"})
logger.info('An info message with %s', 'some parameters',extra={"role":"hacker"})
#hacker的被过滤掉了