关注微信公众号:K哥爬虫,持续分享爬虫进阶、JS/安卓逆向等技术干货!

日志的重要性

日志的作用非常重要,日志可以记录用户的操作、程序的异常,还可以为数据分析提供依据,日志的存在意义就是为了能够在程序在运行过程中记录错误,方便维护和调试,能够快速定位出错的地方,减少维护成本。每个程序员都应该知道,不是为了记录日志而记录日志,日志也不是随意记的。要实现能够只通过日志文件还原整个程序执行的过程,达到能透明地看到程序里执行情况,每个线程、每个过程到底执行到哪的目的。日志就像飞机的黑匣子一样,应当能够复原异常的整个现场乃至细节!

常见日志记录方式

print()

最常见的是把输出函数 print() 当作日志记录的方式,直接打印各种提示信息,常见于个人练习项目里,通常是懒得单独配置日志,而且项目太小不需要日志信息,不需要上线,不需要持续运行,完整的项目不推荐直接打印日志信息,现实中也几乎没有人这么做。

自写模板

我们可以在不少小项目里面看到作者自己写了一个日志模板,通常利用 print() 或者 sys.stdout 稍微封装一下即可实现简单的日志输出,这里的 sys.stdout 是 Python 中的标准输出流,print() 函数是对 sys.stdout 的高级封装,当我们在 Python 中打印对象调用 print(obj) 时候,事实上是调用了 sys.stdout.write(obj+'\n')print() 将内容打印到了控制台,然后追加了一个换行符 \n

自写日志模板适合比较小的项目,可以按照自己的喜好编写模板,不需要太多复杂配置,方便快捷,但是这种记录日志的方式并不是很规范,有可能你自己觉得阅读体验不错,但是别人在接触你的项目的时候往往需要花费一定的时间去学习日志的逻辑、格式、输出方式等,比较大的项目同样不推荐这种方法。

一个简单的自写日志模板举例:

日志模板 log.py:

import sys
import traceback
import datetime def getnowtime():
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def _log(content, level, *args):
sys.stdout.write("%s - %s - %s\n" % (getnowtime(), level, content))
for arg in args:
sys.stdout.write("%s\n" % arg) def debug(content, *args):
_log(content, 'DEBUG', *args) def info(content, *args):
_log(content, 'INFO', *args) def warn(content, *args):
_log(content, 'WARN', *args) def error(content, *args):
_log(content, 'ERROR', *args) def exception(content):
sys.stdout.write("%s - %s\n" % (getnowtime(), content))
traceback.print_exc(file=sys.stdout)

调用日志模块:

import log

log.info("This is log info!")
log.warn("This is log warn!")
log.error("This is log error!")
log.debug("This is log debug!") people_info = {"name": "Bob", "age": 20} try:
gender = people_info["gender"]
except Exception as error:
log.exception(error)

日志输出:

2021-10-19 09:50:58 - INFO - This is log info!
2021-10-19 09:50:58 - WARN - This is log warn!
2021-10-19 09:50:58 - ERROR - This is log error!
2021-10-19 09:50:58 - DEBUG - This is log debug!
2021-10-19 09:50:58 - 'gender'
Traceback (most recent call last):
File "D:/python3Project/test.py", line 18, in <module>
gender = people_info["gender"]
KeyError: 'gender'

Logging

在一个完整的项目中,大多数人都会引入专门的日志记录库,而 Python 自带的标准库 logging 就是专门为日志记录而生的,logging 模块定义的函数和类为应用程序和库的开发实现了一个灵活的事件日志系统。由标准库模块提供日志记录 API 的关键好处是所有 Python 模块都可以使用这个日志记录功能。所以,你的应用日志可以将你自己的日志信息与来自第三方模块的信息整合起来。

logging 模块虽然强大,但是其配置也是比较繁琐的,在大型项目中通常需要单独初始化日志、配置日志格式等等,K哥在日常使用中通常都会对 logging 做如下的封装写法,使日志可以按天保存,保留15天的日志,可以配置是否输出到控制台和文件,如下所示:

# 实现按天分割保留日志

import os
import sys
import logging
from logging import handlers PARENT_DIR = os.path.split(os.path.realpath(__file__))[0] # 父目录
LOGGING_DIR = os.path.join(PARENT_DIR, "log") # 日志目录
LOGGING_NAME = "test" # 日志文件名 LOGGING_TO_FILE = True # 日志输出文件
LOGGING_TO_CONSOLE = True # 日志输出到控制台 LOGGING_WHEN = 'D' # 日志文件切分维度
LOGGING_INTERVAL = 1 # 间隔少个 when 后,自动重建文件
LOGGING_BACKUP_COUNT = 15 # 日志保留个数,0 保留所有日志
LOGGING_LEVEL = logging.DEBUG # 日志等级
LOGGING_suffix = "%Y.%m.%d.log" # 旧日志文件名 # 日志输出格式
LOGGING_FORMATTER = "%(levelname)s - %(asctime)s - process:%(process)d - %(filename)s - %(name)s - line:%(lineno)d - %(module)s - %(message)s" def logging_init():
if not os.path.exists(LOGGING_DIR):
os.makedirs(LOGGING_DIR)
logger = logging.getLogger()
logger.setLevel(LOGGING_LEVEL)
formatter = logging.Formatter(LOGGING_FORMATTER) if LOGGING_TO_FILE:
file_handler = handlers.TimedRotatingFileHandler(filename=os.path.join(LOGGING_DIR, LOGGING_NAME), when=LOGGING_WHEN, interval=LOGGING_INTERVAL, backupCount=LOGGING_BACKUP_COUNT)
file_handler.suffix = LOGGING_suffix
file_handler.setFormatter(formatter)
logger.addHandler(file_handler) if LOGGING_TO_CONSOLE:
stream_handler = logging.StreamHandler(sys.stderr)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler) def logging_test():
logging.info("This is log info!")
logging.warning("This is log warn!")
logging.error("This is log error!")
logging.debug("This is log debug!")
people_info = {"name": "Bob", "age": 20} try:
gender = people_info["gender"]
except Exception as error:
logging.exception(error) if __name__ == "__main__":
logging_init()
logging_test()

输出日志:

INFO - 2021-10-19 11:28:10,103 - process:15144 - test.py - root - line:52 - test - This is log info!
WARNING - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:53 - test - This is log warn!
ERROR - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:54 - test - This is log error!
DEBUG - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:55 - test - This is log debug!
ERROR - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:61 - test - 'gender'
Traceback (most recent call last):
File "D:/python3Project/test.py", line 59, in logging_test
gender = people_info["gender"]
KeyError: 'gender'

它在控制台中是这样的:

当然,如果你不需要很复杂的功能,希望简洁一点,仅仅需要在控制台输出一下日志的话,也可以只进行简单的配置:

import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logging.getLogger()

更优雅的解决方案:Loguru

对于 logging 模块,即便是简单的使用,也需要自己定义格式,这里介绍一个更加优雅、高效、简洁的第三方模块:loguru,官方的介绍是:Loguru is a library which aims to bring enjoyable logging in Python. Loguru 旨在为 Python 带来愉快的日志记录。这里引用官方的一个 GIF 来快速演示其功能:

安装

Loguru 仅支持 Python 3.5 及以上的版本,使用 pip 安装即可:

pip install loguru

开箱即用

Loguru 的主要概念是只有一个:logger

from loguru import logger

logger.info("This is log info!")
logger.warning("This is log warn!")
logger.error("This is log error!")
logger.debug("This is log debug!")

控制台输出:

可以看到不需要手动设置,Loguru 会提前配置一些基础信息,自动输出时间、日志级别、模块名、行号等信息,而且根据等级的不同,还自动设置了不同的颜色,方便观察,真正做到了开箱即用!

add() / remove()

如果想自定义日志级别,自定义日志格式,保存日志到文件该怎么办?与 logging 模块不同,不需要 Handler,不需要 Formatter,只需要一个 add() 函数就可以了,例如我们想把日志储存到文件:

from loguru import logger

logger.add('test.log')
logger.debug('this is a debug')

我们不需要像 logging 模块一样再声明一个 FileHandler 了,就一行 add() 语句搞定,运行之后会发现目录下 test.log 里面同样出现了刚刚控制台输出的 debug 信息。

add() 语句相反,remove() 语句可以删除我们添加的配置:

from loguru import logger

log_file = logger.add('test.log')
logger.debug('This is log debug!')
logger.remove(log_file)
logger.debug('This is another log debug!')

此时控制台会输出两条 debug 信息:

2021-10-19 13:53:36.610 | DEBUG    | __main__:<module>:86 - This is log debug!
2021-10-19 13:53:36.611 | DEBUG | __main__:<module>:88 - This is another log debug!

而 test.log 日志文件里面只有一条 debug 信息,原因就在于我们在第二条 debug 语句之前使用了 remove() 语句。

完整参数

Loguru 对输出到文件的配置有非常强大的支持,比如支持输出到多个文件,分级别分别输出,过大创建新文件,过久自动删除等等。 下面我们来详细看一下 add() 语句的详细参数:

基本语法:

add(sink, *, level='DEBUG', format='<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>', filter=None, colorize=None, serialize=False, backtrace=True, diagnose=True, enqueue=False, catch=True, **kwargs)

基本参数释义:

  • sink:可以是一个 file 对象,例如 sys.stderropen('file.log', 'w'),也可以是 str 字符串或者 pathlib.Path 对象,即文件路径,也可以是一个方法,可以自行定义输出实现,也可以是一个 logging 模块的 Handler,比如 FileHandler、StreamHandler 等,还可以是 coroutine function,即一个返回协程对象的函数等。
  • level:日志输出和保存级别。
  • format:日志格式模板。
  • filter:一个可选的指令,用于决定每个记录的消息是否应该发送到 sink。
  • colorize:格式化消息中包含的颜色标记是否应转换为用于终端着色的 ansi 代码,或以其他方式剥离。 如果没有,则根据 sink 是否为 tty(电传打字机缩写) 自动做出选择。
  • serialize:在发送到 sink 之前,是否应首先将记录的消息转换为 JSON 字符串。
  • backtrace:格式化的异常跟踪是否应该向上扩展,超出捕获点,以显示生成错误的完整堆栈跟踪。
  • diagnose:异常跟踪是否应显示变量值以简化调试。建议在生产环境中设置 False,避免泄露敏感数据。
  • enqueue:要记录的消息是否应在到达 sink 之前首先通过多进程安全队列,这在通过多个进程记录到文件时很有用,这样做的好处还在于使日志记录调用是非阻塞的。
  • catch:是否应自动捕获 sink 处理日志消息时发生的错误,如果为 True,则会在 sys.stderr 上显示异常消息,但该异常不会传播到 sink,从而防止应用程序崩溃。
  • **kwargs:仅对配置协程或文件接收器有效的附加参数(见下文)。

当且仅当 sink 是协程函数时,以下参数适用:

  • loop:将在其中调度和执行异步日志记录任务的事件循环。如果为 None,将使用 asyncio.get_event_loop() 返回的循环。

当且仅当 sink 是文件路径时,以下参数适用:

  • rotation:一种条件,指示何时应关闭当前记录的文件并开始新的文件。
  • **retention **:过滤旧文件的指令,在循环或程序结束期间会删除旧文件。
  • compression:日志文件在关闭时应转换为的压缩或存档格式。
  • delay:是在配置 sink 后立即创建文件,还是延迟到第一条记录的消息时再创建。默认为 False
  • mode:内置 open() 函数的打开模式,默认为 a(以追加模式打开文件)。
  • buffering:内置 open() 函数的缓冲策略,默认为1(行缓冲文件)。
  • encoding:内置 open() 函数的文件编码,如果 None,则默认为 locale.getpreferredencoding()
  • **kwargs:其他传递给内置 open() 函数的参数。

这么多参数可以见识到 add() 函数的强大之处,仅仅一个函数就能实现 logging 模块的诸多功能,接下来介绍几个比较常用的方法。

rotation 日志文件分隔

add() 函数的 rotation 参数,可以实现按照固定时间创建新的日志文件,比如设置每天 0 点新创建一个 log 文件:

logger.add('runtime_{time}.log', rotation='00:00')

设置超过 500 MB 新创建一个 log 文件:

logger.add('runtime_{time}.log', rotation="500 MB")

设置每隔一个周新创建一个 log 文件:

logger.add('runtime_{time}.log', rotation='1 week')

retention 日志保留时间

add() 函数的 retention 参数,可以设置日志的最长保留时间,比如设置日志文件最长保留 15 天:

logger.add('runtime_{time}.log', retention='15 days')

设置日志文件最多保留 10 个:

logger.add('runtime_{time}.log', retention=10)

也可以是一个 datetime.timedelta 对象,比如设置日志文件最多保留 5 个小时:

import datetime
from loguru import logger logger.add('runtime_{time}.log', retention=datetime.timedelta(hours=5))

compression 日志压缩格式

add() 函数的 compression 参数,可以配置日志文件的压缩格式,这样可以更加节省存储空间,比如设置使用 zip 文件格式保存:

logger.add('runtime_{time}.log', compression='zip')

其格式支持:gzbz2xzlzmatartar.gztar.bz2tar.xz

字符串格式化

Loguru 在输出 log 的时候还提供了非常友好的字符串格式化功能,相当于 str.format()

logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')

输出:

2021-10-19 14:59:06.412 | INFO     | __main__:<module>:3 - If you are using Python 3.6, prefer f-strings of course!

异常追溯

在 Loguru 里可以直接使用它提供的装饰器就可以直接进行异常捕获,而且得到的日志是无比详细的:

from loguru import logger

@logger.catch
def my_function(x, y, z):
# An error? It's caught anyway!
return 1 / (x + y + z) my_function(0, 0, 0)

日志输出:

2021-10-19 15:04:51.675 | ERROR    | __main__:<module>:10 - An error has been caught in function '<module>', process 'MainProcess' (30456), thread 'MainThread' (26268):
Traceback (most recent call last): > File "D:/python3Project\test.py", line 10, in <module>
my_function(0, 0, 0)
└ <function my_function at 0x014CDFA8> File "D:/python3Project\test.py", line 7, in my_function
return 1 / (x + y + z)
│ │ └ 0
│ └ 0
└ 0 ZeroDivisionError: division by zero

在控制台的输出是这样的:

相比 Logging,Loguru 无论是在配置方面、日志输出样式还是异常追踪,都远优于 Logging,使用 Loguru 无疑能提升开发人员效率。本文仅介绍了一些常用的方法,想要详细了解可参考 Loguru 官方文档或关注 Loguru GitHub

Loguru:Python 日志终极解决方案的更多相关文章

  1. android软键盘弹出引起的各种不适终极解决方案

    android软键盘弹出引起的各种不适终极解决方案 以下描述如何解决ListView高度小于0时出现的UI问题. 创建RelativeLayout的子类TxrjRelativeLayout publi ...

  2. Python 日志输出中添加上下文信息

    Python日志输出中添加上下文信息 除了传递给日志记录函数的参数(如msg)外,有时候我们还想在日志输出中包含一些额外的上下文信息.比如,在一个网络应用中,可能希望在日志中记录客户端的特定信息,如: ...

  3. 终极解决方案: Invalid character found in the request target.

    终极解决方案:(导出可能出现) 我的tomcat版本是8.5.32,导出时遇到以下报错. 报错日志: Invalid character found in the request target. Th ...

  4. python日志篇-基础版

    对常用python日志语法做记录,方便以后重复使用 print内容记录到文件: #!/usr/bin/env python # -*- coding: utf-8 -*- ##____________ ...

  5. Eclipse不自动编译java文件的终极解决方案

    最近我的eclipse经常犯傻,项目中总是有很多,启动项目也是没有启动类.查了下项目中生成的class文件,我靠竟然没有,或者还是以前的.原来是eclipse犯傻了,它没帮我自动编译java文件.一般 ...

  6. 【转】JSP中文乱码问题终极解决方案

    原文地址:http://blog.csdn.net/beijiguangyong/article/details/7414247 在介绍方法之前我们首先应该清楚具体的问题有哪些,笔者在本博客当中论述的 ...

  7. Xcode-调试断点不能停在代码区终极解决方案

    转发 调试断点不能停在代码区终极解决方案:  http://mobile.51cto.com/iphone-390082.htm

  8. VIM、GVIM在WINDOWS下中文乱码的终极解决方案

    文章转自:http://www.liuhuadong.com/archives/68 vim.gvim在windows下中文乱码的终极解决方案在windows下vim的中文字体显示并不好,所以我们需要 ...

  9. Android大图片裁剪终极解决方案(上:原理分析)

    转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-)  http://my.oschina.net/ryanhoo/blog/86842 约几个月前,我正 ...

  10. Python日志输出——logging模块

    Python日志输出——logging模块 标签: loggingpythonimportmodulelog4j 2012-03-06 00:18 31605人阅读 评论(8) 收藏 举报 分类: P ...

随机推荐

  1. 对话 BitSail Contributor | 姚泽宇:新生火焰,未来亦可燎原

    2022 年 10 月,字节跳动 BitSail 数据引擎正式开源.同期,社区推出 Contributor 激励计划第一期,目前已有 12 位开发者为 BitSail 社区做出贡献,成为了首批 Bit ...

  2. Solon 开发进阶,四、启动参数说明

    Solon 开发进阶 一.插件扩展机制 二.体外扩展机制 三.常用配置说明 四.启动参数说明 五.全局异常订阅 启动参数,在应用启动后会被静态化(为了内部更高效的利用).比如,想通过体外扩展加载配置, ...

  3. 取消 SQL Server 密码复杂度

    可以先设置一个复杂密码,安装完成后,进入数据库,执行下列命令,关闭复杂密码策略及修改简单密码 ALTER LOGIN sa WITH PASSWORD = '新密码', CHECK_POLICY = ...

  4. Appium常用定位方法讲解

    Appium常用定位方法讲解 对象定位是自动化测试中很关键的一步,也可以说是 最关键的一步,毕竟你对象都没定位那么你想操作也不行,下面我们来看常用的一些定位方式. ID定位(取resource-id的 ...

  5. vim-修改键盘Tab键为四个空格

    在 /etc/vimrc 文件后添加一行 set ts=4

  6. 【HZERO】数据导入

    #数据导入 ##文档整理 通用导入客户端 https://open.hand-china.com/hzero-docs/v1.3/zh/docs/service/import/import/ 开放平台 ...

  7. SpringBoot 学习笔记:运维篇

    SpringBoot程序的打包和运行 开发部门使用Git.SVN等版本控制工具上传工程到版本服务器 服务器使用版本控制工具下载工程 服务器上使用Maven工具在当前真机环境下重新构建项目 启动服务 程 ...

  8. VS2019 16.8下载安装

    昨天,想升级到 net5版本,发现在vs2019上始终发现不了它,考虑好几个小时,终于狠下心,把vs2019卸载了,重新装一次.耗费了一个晚上,先将安装过程写下来,以供参考 第一步, 到 微软官网 h ...

  9. 广搜 广搜 poj 3984

    ***求最短路径,然后再输出路径, BFS+路径输出*** #include <iostream> #include <cstdio> #include <cstring ...

  10. C语言基础之第一个C程序

    前言 在开始学习 C 语言的基础知识之前,我们需要学习如何编写.编译和运行第一个 C 程序. 要编写第一个 C 程序,打开 C 控制台并编写以下代码,我这里直接使用vs2022进行代码的编写: #in ...