简介
日志是工程中不可缺少的一部分,国家等保2.0也规定,至少保留日志180天。对于程序员来说,日志也方便进行记录及排错。
logging是Python自带的日志模块,主要有以下几个部分:
- 记录器暴露了应用程序代码直接使用的接口。
- 处理器将日志记录(由记录器创建)发送到适当的目标。
- 过滤器提供了更细粒度的功能,用于确定要输出的日志记录。
- 格式器指定最终输出中日志记录的样式。
日志级别
日志级别
级别 |
数值 |
|
50 |
|
40 |
|
30 |
|
20 |
|
10 |
|
0 |
import logging之后可以从里面找到,用于区分日志记录的级别,通过级别可以确定是否输出。
记录器
setLevel
(level)
给记录器设置阈值为 level 。日志等级小于 level 会被忽略。严重性为 level 或更高的日志消息将由该记录器的任何一个或多个处理器发出,除非将处理器的级别设置为比 level 更高的级别。
创建记录器时,级别默认设置为 NOTSET。
addHandler
(hdlr)
将指定的处理器 hdlr 添加到此记录器。
addFilter
(filter)
将指定的过滤器 filter 添加到此记录器。
debug
(msg, *args, **kwargs)
在此记录器上记录 DEBUG
级别的消息。
info
(msg, *args, **kwargs)
在此记录器上记录 INFO
级别的消息。
warning
(msg, *args, **kwargs)
在此记录器上记录 WARNING
级别的消息。
error
(msg, *args, **kwargs)
在此记录器上记录 ERROR
级别的消息。
critical
(msg, *args, **kwargs)
在此记录器上记录 CRITICAL
级别的消息。
log
(level, msg, *args, **kwargs)
在此记录器上记录 level 整数代表的级别的消息。
处理器
StreamHandler
它可将日志记录输出发送到数据流例如 sys.stdout, sys.stderr 或任何文件类对象(或者更精确地说,任何支持 write()
和 flush()
方法的对象)。
FileHandler
它可将日志记录输出到磁盘文件中。
格式器
Formatter
(fmt=None, datefmt=None, style='%', validate=True, *, defaults=None)
属性名称 |
格式 |
描述 |
args |
此属性不需要用户进行格式化。 |
合并到 |
asctime |
|
被创建的供人查看时间值。 默认形式为 '2003-07-08 16:49:45,896' (逗号之后的数字为时间的毫秒部分)。 |
created |
|
被创建的时间。 |
exc_info |
此属性不需要用户进行格式化。 |
异常元组(例如 |
filename |
|
|
funcName |
|
函数名包括调用日志记录. |
levelname |
|
消息文本记录级别( |
levelno |
|
消息数字的记录级别 ( |
lineno |
|
发出日志记录调用所在的源行号(如果可用)。 |
message |
|
记入日志的消息,即 |
module -- 模块 |
|
模块 ( |
msecs |
|
被创建的时间的毫秒部分。 |
msg |
此属性不需要用户进行格式化。 |
在原始日志记录调用中传入的格式字符串。 与 |
name |
|
用于记录调用的日志记录器名称。 |
pathname |
|
发出日志记录调用的源文件的完整路径名(如果可用)。 |
process |
|
进程ID(如果可用) |
processName |
|
进程名(如果可用) |
relativeCreated |
|
以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。 |
stack_info |
此属性不需要用户进行格式化。 |
当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。 |
thread |
|
线程ID(如果可用) |
threadName |
|
线程名(如果可用) |
一般常用的就是时间,级别,内容,可以在团队内自行规定msg的格式作为补充。
多线程与多进程安全
logging是多线程安全的,所以添加多进程安全即可
代码
""" -*- coding:utf-8 -*- @File: log.py @Author:frank yu @DateTime: 2021.10.12 20:56 @Contact: frankyu112058@gmail.com @Description: """ import logging import os import random import sys import multiprocessing import time from shutil import copyfile from colorlog import ColoredFormatter # log directory LOG_DIR = "./logs" # level fot stdout STD_LEVEL = logging.WARN # level for file FILE_LEVEL = logging.DEBUG CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0 class Log: def __init__(self, log_dir=LOG_DIR): """ function:init create log file, judge if log file is too bigger :param log_dir: directory of logs """ log_path = log_dir + "/log" if not os.path.exists(log_dir): os.mkdir(log_dir) os.chmod(log_dir, 0o777) if not os.path.exists(log_path): f = open(log_path, mode='w', encoding='utf-8') f.close() os.chmod(log_path, 0o777) # if log file is more than 1MB, copy to a file and clear log file if os.path.getsize(log_path) / 1048576 > 1: # print(os.path.getsize(log_path)) copyfile(log_path, log_dir + "/log" + str(time.time()).replace(".", "")) with open(log_path, 'w') as f: f.truncate() f.close() self.logger_format = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s') self.c_logger_format = ColoredFormatter(fmt='%(log_color)s%(asctime)s' ' - %(log_color)s%(levelname)s: %(log_color)s%(message)s', reset=True, secondary_log_colors={}, style='%' ) self.logger = logging.getLogger(str(random.random())) self.logger.handlers.clear() self.logger.setLevel(logging.DEBUG) self.filehandler = logging.FileHandler(log_path, mode='a') self.filehandler.setLevel(FILE_LEVEL) self.filehandler.setFormatter(self.logger_format) self.stdouthandler = logging.StreamHandler(sys.stdout) self.stdouthandler.setLevel(STD_LEVEL) self.stdouthandler.setFormatter(self.c_logger_format) self.logger.addHandler(self.stdouthandler) self.logger.addHandler(self.filehandler) self.__lock = multiprocessing.Lock() def __log(self, msg=None, level=logging.INFO): if level == logging.DEBUG: self.logger.debug(msg) elif level == logging.INFO: self.logger.info(msg) elif level == logging.WARNING: self.logger.warning(msg) elif level == logging.ERROR: self.logger.error(msg) elif level == logging.CRITICAL: self.logger.critical(msg) def log_show_store(self, msg, level): """ function: show logs on screen and store logs into log file :param msg:message of log :param level:level of log return: None """ self.__lock.acquire() self.logger.addHandler(self.stdouthandler) self.logger.addHandler(self.filehandler) self.__log(msg=msg, level=level) self.__lock.release() def log_show(self, msg, level): """ function:show msg on the screen :param msg:message of log :param level: level of log """ self.__lock.acquire() self.logger.removeHandler(self.filehandler) self.logger.addHandler(self.stdouthandler) self.__log(msg, level=level) self.logger.removeHandler(self.stdouthandler) self.logger.addHandler(self.filehandler) self.__lock.release() def log_store(self, msg, level): """ function:store msg into log file :param msg:message of log :param level: level of log """ self.__lock.acquire() self.logger.removeHandler(self.stdouthandler) self.logger.addHandler(self.filehandler) self.__log(msg, level=level) self.logger.removeHandler(self.filehandler) self.logger.addHandler(self.stdouthandler) self.__lock.release() def log_wrapper(self, *args): """ function: record arg and result of functions :param args: :return: wrapper """ self.log_store(' '.join(args), FILE_LEVEL) def wrapper(func): def inner(*args, **kwargs): self.log_store(getattr(func, "__name__") + " call " + str(args) + str(kwargs), 10) ret = func(*args, **kwargs) self.log_store(getattr(func, "__name__") + " return " + str(ret), 10) return ret return inner return wrapper
导入及全局变量
colorlog需要安装一下,可以控制台输出彩色的
multiprocessing用于进程安全,加锁
设置了几个全局变量,方便修改
LOG_DIR是日志保存的目录
STD_LEVEL是控制台输出的级别,只输出WARN及以上的
FILE_LEVEL是保存日志文件的级别,一般调试时使用DEBUG,生产环境时使用INFO
函数
__init__
- 创建目录和日志文件,如果不存在。若大于1M,复制为当前日期时间的日志文件
- 设置了控制台和文件的格式器,控制台是彩色的
- 设置了控制台和文件的处理器
log_show_store
保存到日志文件并打印到控制台
log_show
仅打印到控制台
log_store
仅保存到日志文件
log_wrapper
日志装饰器,用于添加到重要函数上,记录函数调用参数及返回值
实验及结果
""" -*- coding:utf-8 -*- @File: main.py @Author:frank yu @DateTime: 2021.10.12 20:56 @Contact: frankyu112058@gmail.com @Description: """ from utils.log import Log, DEBUG, WARN, INFO from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor log = Log() @log.log_wrapper("wrapper for test") def test(a, b, c=5): log.log_show('main test 1 ' + str(a), level=DEBUG) log.log_show('main test 2 ' + str(b), level=WARN) log.log_store('main test 3 ' + str(a), level=DEBUG) log.log_store('main test 4 ' + str(b), level=WARN) log.log_show_store('main test 5 ' + str(a + b), level=INFO) return a + b + c def even(num): log.log_store('main even 1 ' + str(num), level=INFO) def odd(num): log.log_store('main odd 1 ' + str(num), level=INFO) # multi thread test def multi_thread(): exe = ThreadPoolExecutor(4) for i in range(10): if i & 1 == 0: exe.submit(even, i) else: exe.submit(odd, i) # multi process test def multi_process(): exe = ProcessPoolExecutor(4) for i in range(10): if i & 1 == 0: exe.submit(even, i) else: exe.submit(odd, i) if __name__ == '__main__': test(3, 4, 6) multi_thread() multi_process()
msg这里传的是 模块 函数 函数行数 消息 ,程序出错方便定位。
test测试了日志级别和日志装饰器
multi_thread测试了多线程
multi_process测试了多进程
结果如下:
测试结果
可以看到控制台仅输出了WARNING级别,当然,如果有更高级别也会输出。
可以看到test函数的调用及参数和返回结果
可以看到多线程和多进程状态下结果没有丢失
更新
用了段时间,彩色的花里胡哨,很少用到,formatter有点少,而且封装后不能定位打印日志的文件,还是直接使用logger吧。
log.py
""" -*- coding:utf-8 -*- @File: log.py @Author:frank yu @DateTime: 2023.08.17 15:07 @Contact: frankyu112058@gmail.com @Description: 日志封装 1. 可以多线程、多进程使用 2. 默认只有控制台handler,可以通过set_std、set_file、set_std_file设置,尤其是想保存到文件时 3. 其他地方使用只import log对象即可 """ import functools import logging import os import random import sys import multiprocessing import time from shutil import copyfile # log directory LOG_DIR = "./logs" # level for stdout STD_LEVEL = logging.DEBUG # level for file FILE_LEVEL = logging.DEBUG class Log: def __init__(self, log_dir=LOG_DIR): """ function:init create log file, judge if log file is too bigger :param log_dir: directory of logs """ log_path = log_dir + "/log" if not os.path.exists(log_dir): os.mkdir(log_dir) os.chmod(log_dir, 0o777) if not os.path.exists(log_path): f = open(log_path, mode='w', encoding='utf-8') f.close() os.chmod(log_path, 0o777) # if log file is more than 1MB, copy to a file and clear log file if os.path.getsize(log_path) / 1048576 > 1: # print(os.path.getsize(log_path)) copyfile(log_path, log_dir + "/log" + str(time.time()).replace(".", "")) with open(log_path, 'w') as f: f.truncate() f.close() self.logger_format = logging.Formatter('[%(asctime)s %(filename)s:%(funcName)s:%(lineno)d %(levelname)-8s] %(' 'message)s') self.logger = logging.getLogger(str(random.random())) self.logger.handlers.clear() self.logger.setLevel(logging.DEBUG) self.filehandler = logging.FileHandler(log_path, mode='a') self.filehandler.setLevel(FILE_LEVEL) self.filehandler.setFormatter(self.logger_format) self.stdouthandler = logging.StreamHandler(sys.stdout) self.stdouthandler.setLevel(STD_LEVEL) self.stdouthandler.setFormatter(self.logger_format) self.logger.addHandler(self.stdouthandler) self.__lock = multiprocessing.Lock() def set_std_file(self): """ function: set std and file handler return: None """ self.__lock.acquire() self.logger.addHandler(self.stdouthandler) self.logger.addHandler(self.filehandler) self.__lock.release() def set_std(self): """ function: set std handler """ self.__lock.acquire() self.logger.removeHandler(self.filehandler) self.logger.addHandler(self.stdouthandler) self.__lock.release() def set_file(self): """ function: set file handler """ self.__lock.acquire() self.logger.removeHandler(self.stdouthandler) self.logger.addHandler(self.filehandler) self.__lock.release() def log_wrapper(self, func): """ function: record arg and result of functions :param func: 自动获取名字,不需要填写 :return: wrapper """ @functools.wraps(func) def wrapper(*args, **kwargs): self.logger.debug(getattr(func, "__name__") + " call " + str(args) + str(kwargs)) ret = func(*args, **kwargs) self.logger.debug(getattr(func, "__name__") + " return " + str(ret)) return ret return wrapper log = Log()
参考
更多python相关内容:【python总结】python学习框架梳理
有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。如果您感觉有所收获,自愿打赏,可选择支付宝18833895206(小于),您的支持是我不断更新的动力。