python日志模块logging

一.日志级别

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使用

  1. 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,  # 是否让日志信息继续冒泡给其他的日志处理系统
            },
        }
    }
    
  2. 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)
    
    
  3. 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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值