目录
一.日志级别
CRITICAL = 50 #FATAL = CRITICAL
ERROR = 40
WARNING = 30 #WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 #不设置
二.简单使用
import logging
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')
'''
WARNING:root:警告warn
ERROR:root:错误error
CRITICAL:root:严重critical
'''
三.logging模块的Formatter,Handler,Logger,Filter对象
#logger:产生日志的对象
#Filter:过滤日志的对象
#Handler:接收日志然后控制打印到不同的地方,FileHandler用来打印到文件中,StreamHandler用来打印到终端
#Formatter对象:可以定制不同的日志格式对象,然后绑定给不同的Handler对象使用,以此来控制不同的Handler的日志格式
四.日志配置字典
"""
logging配置
"""
import os
# 1、定义三种日志输出格式,日志中可能用到的格式化串如下
# %(name)s Logger的名字
# %(levelno)s 数字形式的日志级别
# %(levelname)s 文本形式的日志级别
# %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
# %(filename)s 调用日志输出函数的模块的文件名
# %(module)s 调用日志输出函数的模块名
# %(funcName)s 调用日志输出函数的函数名
# %(lineno)d 调用日志输出函数的语句所在的代码行
# %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
# %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
# %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
# %(thread)d 线程ID。可能没有
# %(threadName)s 线程名。可能没有
# %(process)d 进程ID。可能没有
# %(message)s用户输出的消息
# 2、强调:其中的%(name)s为getlogger时指定的名字
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
'[%(levelname)s][%(message)s]'
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
test_format = '%(asctime)s] %(message)s'
# 3、日志配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
'test': {
'format': test_format
},
},
'filters': {},
'handlers': {
#打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
#打印到文件的日志,收集info及以上的日志
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,日志轮转
'formatter': 'standard',
# 可以定制日志文件路径
# BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # log文件的目录
# LOG_PATH = os.path.join(BASE_DIR,'a1.log')
'filename': 'a1.log', # 日志文件
'maxBytes': 1024*1024*5, # 日志大小 5M
'backupCount': 5,
'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
},
'other': {
'level': 'DEBUG',
'class': 'logging.FileHandler', # 保存到文件
'formatter': 'test',
'filename': 'a2.log',
'encoding': 'utf-8',
},
},
'loggers': {
#logging.getLogger(__name__)拿到的logger配置
'': {
'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
},
'用户交易': {
'handlers': ['other',],
'level': 'DEBUG',
'propagate': False,
},
},
}
五.使用
import settings
# !!!强调!!!
# 1、logging是一个包,需要使用其下的config、getLogger,可以如下导入
# from logging import config
# from logging import getLogger
# 2、也可以使用如下导入
import logging.config # 这样连同logging.getLogger都一起导入了,然后使用前缀logging.config.
# 3、加载配置
logging.config.dictConfig(settings.LOGGING_DIC)
# 4、输出日志
logger1=logging.getLogger('用户交易')
logger1.info('吉祥村产生了20亿GDP')
# logger2=logging.getLogger('专门的采集') # 名字传入的必须是'专门的采集',与LOGGING_DIC中的配置唯一对应
# logger2.debug('专门采集的日志')
六.Logger与Handler的级别
可以给logger和handler同时设置level
但是需要注意的是logger是第一级过滤,然后才能到handler。
七.django使用
-
dev.py
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(module)s %(lineno)d %(message)s' }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { # 实际开发建议使用WARNING 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { # 实际开发建议使用ERROR 'level': 'ERROR', 'class': 'logging.handlers.RotatingFileHandler', # 日志位置,日志文件名,日志保存目录必须手动创建,注:这里的文件路径要注意BASE_DIR代表的是小luffyapi 'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"), # 日志文件的最大值,这里我们设置300M 'maxBytes': 300 * 1024 * 1024, # 日志文件的数量,设置最大日志数量为10 'backupCount': 10, # 日志格式:详细格式 'formatter': 'verbose', # 文件内容编码 'encoding': 'utf-8' }, }, # 日志对象 'loggers': { 'django': { 'handlers': ['console', 'file'], 'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统 }, } }
-
exceptions.py
from rest_framework import status from rest_framework.views import exception_handler from luffyapi.utils import response from .logger import log from corsheaders.middleware import CorsMiddleware # 写日志 # from . import response 从当前路径导入response def common_exception_handler(exc, context): # context['view'] 是TextView的对象 # context['view'].__class__.__name__拿出错误的类 log.error('view视图: %s-------error错误: %s' % (context['view'].__class__.__name__, str(exc))) res = exception_handler(exc, context) # res是个Response对象,内部有个data if not res: # 系统处理不了的,直接返回 return response.ApiResponse(code=0, msg='error', result=str(exc),status=status.HTTP_400_BAD_REQUEST) else: # 已知错误,顺手给data返回 return response.ApiResponse(code=0, msg='error', result=res.data,status=status.HTTP_400_BAD_REQUEST)
-
logger.py
import logging # log=logging.getLogger('名字') 要和配置文件下的名字一样 log = logging.getLogger('django')
八. json日志格式化器
以上的日志输出格式都是普通字符串,对于日志处理系统,如ELK,ELFK…不通用,因此要将其转为json格式字符串。
可以自定义一个JsonFormatter,并将其应用到你的配置中。以下是一个示例:
import json
class JsonFormatter(logging.Formatter):
def format(self, record):
log_record = {
'timestamp': self.formatTime(record),
'level': record.levelname,
'message': record.getMessage(),
'logger_name': record.name,
'filename': record.filename,
'lineno': record.lineno,
'funcName': record.funcName
}
return json.dumps(log_record)
record对象提供许多上下文相关的字段:
class LogRecord:
# args can be set to None by logging.handlers.QueueHandler
# (see https://bugs.python.org/issue44473)
args: _ArgsType | None
asctime: str
created: float
exc_info: _SysExcInfoType | None
exc_text: str | None
filename: str
funcName: str
levelname: str
levelno: int
lineno: int
module: str
msecs: float
# Only created when logging.Formatter.format is called. See #6132.
message: str
msg: str
name: str
pathname: str
process: int | None
processName: str | None
relativeCreated: float
stack_info: str | None
thread: int | None
threadName: str | None
以下是一个简单的示例:
import logging
import ujson
from logging.config import dictConfig
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
class JsonFormatter(logging.Formatter):
def format(self, record):
log_record = {
'level': record.levelname,
'timestamp': self.formatTime(record),
'caller': f"{record.filename}:{record.lineno} - {record.funcName}",
'message': record.getMessage(),
}
return ujson.dumps(log_record)
# 3、日志配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': simple_format
},
"json_formatter": {
"()": "test.JsonFormatter"
}
},
'filters': {},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'json_formatter'
}
},
'loggers': {
# logging.getLogger(__name__)拿到的logger配置
'': {
'handlers': ['console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
}
},
}
dictConfig(LOGGING_DIC)
logger = logging.getLogger(__name__)
def view():
logger.info("hello")
if __name__ == '__main__':
view()
输出的日志格式:
{"level":"INFO","timestamp":"2023-10-10 23:25:44,508","caller":"test.py:55 - view","message":"hello"}
为什么key是"()"
?
'json': {'()': 'path.to.JsonFormatter'}
这里的'()'
是一个用于在Python中调用可调用对象(callable)的特殊语法。
在这个上下文中,'()'
的含义是调用一个可调用对象来创建一个新的实例,这里的可调用对象是一个类(JsonFormatter
)。这是因为在Python中,类本身也是可调用对象,用于创建类的实例。
所以,'json': {'()': 'path.to.JsonFormatter'}
这个配置实际上是在告诉Python去调用JsonFormatter
类来创建一个新的实例,并将其用作日志的格式器。
查看源码:
def configure_custom(self, config):
"""Configure an object with a user-supplied factory."""
c = config.pop('()')
if not callable(c):
c = self.resolve(c)
props = config.pop('.', None)
# Check for valid identifiers
kwargs = {k: config[k] for k in config if valid_ident(k)}
result = c(**kwargs)
if props:
for name, value in props.items():
setattr(result, name, value)
return result
如果key是"()"
,就表明c是个可调用对象,也就是上文的test.JsonFormatter
是可调用的,然后执行result = c(**kwargs)
实例化formatter.