Python 日志Logging详解

Logging详解

日志按照下面四个层次来完成日志的功能

  • Loggers expose the interface that application code directly uses.
    Logger暴露出来给应用使用的接口
  • Handlers send the log records (created by loggers) to the appropriate destination.
    Handlers是发送日志记录(由logger创建的)到合适的目的地,包括文件,屏幕,email...
  • Filters provide a finer grained facility for determining which log records to output.
    Filters 是提供一个过滤的机制,决定哪些日志可以留下
  • Formatters specify the layout of log records in the final output.
    Formatters是输出日志的格式
import logging
# 没有创建logger, 默认是root logger, 直接打印在屏幕
# root logger 默认的level 是warning,所以这里设置成debug,才能打印info的日志
# 设置了root logger的format,包括时间,logger名字,levelname等
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(name)s:%(levelname)s:%(message)s')
logging.warning('this is warning')
logging.info('this is info')

"""
#输出:
2018-01-30 15:50:28,761:root:WARNING:this is warning
2018-01-30 15:50:28,761:root:INFO:this is info
"""

# 创建一个新的apps 的logger, 如果logger不设置,就会用root logger那套(打印到屏幕和上面的格式)
# 因为它是会默认传播到祖先logger
logger = logging.getLogger('apps')
logger.setLevel(logging.DEBUG)
# 是否传播这个日志到祖先logger, 如果设置了False 就不会传到root logger(祖先Logger)的
# 默认StreamHandler那里, 也就是不会打印在页面上
logger.propagate = False
# 添加handler, 决定日志落地到哪里,可以多个
# 这个是记录在文件的Handler
apps_handler = logging.FileHandler(filename="apps.log")
# 设置这个handler的处理格式, 实例化一个Formatter对象
apps_formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s')
apps_handler.setFormatter(apps_formatter)
logger.addHandler(apps_handler)
# 日志会打印到apps.log, 并且不会输出到屏幕(如果logger.propagate=True就会)
logger.debug('shit')

# 定义一个新的logger
child_logger = logging.getLogger('apps.owan')
# 因为这个child_logger 是apps.owan, 它是继承了apps这个logger
# 这个child_logger.propagate 默认是True
# 所以还是会传到它的祖先logger 也就是apps
child_logger.info('haha')
# 所以这个info 是会传播到apps 所以apps.log会出现这个日志。
# 这里充分说明logger的继承关系

logging日志流程


logging_flow.png
tail apps.log
# apps ,apps.owan 的logger都把日志写到apps.log了, 而且格式一样
# 因为child_logger没有设置handler 和 formatter, 默认传播到祖先那里apps 的logger
2018-01-30 16:00:10,258:apps:DEBUG:apps logger coming
2018-01-30 16:00:10,258:apps.owan:INFO:i am apps child_logger

下面看一个日志类

import logging
import os
from douban_scrapy import settings
from logging.handlers import RotatingFileHandler
from logging import StreamHandler

# 直接继承logging.Logger 那么就是说这个类就是一个Logger, 有了Logger所有方法
# 只是在类里面添加一些内部方法,让logger 封装addhandler, setformatter等方法
class LogHandler(logging.Logger):
    # 单例模式
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            # 一开始居然用了 cls()来实例化 导致无限次调用
            # cls._instance = cls(*args, **kwargs)
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance

    def __init__(self, name, level=logging.DEBUG, to_stream=True, to_file=True):
        self.name = name
        self.level = level
        self.formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
        # 错误的, 继承了logger 本身就是logger 不用再self.logger=xxx 这样变成了一个新的变量
        #self.logger = logging.Logger(name=name, level=level)
        super(LogHandler, self).__init__(name=name, level=level)

        # 写文件
        if to_file:
            self.__setFileHandler__()

        # 写标准输出
        if to_stream:
            self.__setSteamHandler__()

    def __setSteamHandler__(self):
        stream_handler = StreamHandler()
        stream_handler.setFormatter(self.formatter)
        self.addHandler(stream_handler)

    def __setFileHandler__(self):
        log_path = os.path.join(settings.LOG_DIR, self.name +'.log')
        handler = RotatingFileHandler(log_path, maxBytes=1024, backupCount=5)
        handler.setFormatter(self.formatter)
        self.addHandler(handler)


if __name__ == '__main__':
    logger = LogHandler('scrapy')
    logger2 = LogHandler('scrapy')
    print logger, logger2
    logger.info('haha')

这是别人写的日志类


import os

import logging

from logging.handlers import TimedRotatingFileHandler

# 日志级别
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

CURRENT_PATH = os.path.dirname(os.path.abspath(__file__))
ROOT_PATH = os.path.join(CURRENT_PATH, os.pardir)
LOG_PATH = os.path.join(ROOT_PATH, 'log')


class LogHandler(logging.Logger):
    """
    LogHandler
    """

    def __init__(self, name, level=DEBUG, stream=True, file=True):
        self.name = name
        self.level = level
        logging.Logger.__init__(self, self.name, level=level)
        if stream:
            self.__setStreamHandler__()
        if file:
            self.__setFileHandler__()

    def __setFileHandler__(self, level=None):
        """
        set file handler
        :param level:
        :return:
        """
        file_name = os.path.join(LOG_PATH, '{name}.log'.format(name=self.name))
        # 设置日志回滚, 保存在log目录, 一天保存一个文件, 保留15天
        file_handler = TimedRotatingFileHandler(filename=file_name, when='D', interval=1, backupCount=15)
        file_handler.suffix = '%Y%m%d.log'
        if not level:
            file_handler.setLevel(self.level)
        else:
            file_handler.setLevel(level)
        formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')

        file_handler.setFormatter(formatter)
        self.file_handler = file_handler
        self.addHandler(file_handler)

    def __setStreamHandler__(self, level=None):
        """
        set stream handler
        :param level:
        :return:
        """
        stream_handler = logging.StreamHandler()
        formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
        stream_handler.setFormatter(formatter)
        if not level:
            stream_handler.setLevel(self.level)
        else:
            stream_handler.setLevel(level)
        self.addHandler(stream_handler)

    def resetName(self, name):
        """
        reset name
        :param name:
        :return:
        """
        self.name = name
        self.removeHandler(self.file_handler)
        self.__setFileHandler__()


if __name__ == '__main__':
    log = LogHandler('test')
    log.info('this is a test msg')

定义Log两种方法

  • 第一种:就是实例化logger = logging.logger 然后手动给logger添加addHandler, addFilter, handler.setFormatter 添加格式,这样的形式来获取logger
  • 第二种:就是使用 logging.config.dictConfig 来从配置文件生成logger

root logger

# 获取root logger 
root_logger = logging.getLogger() 
print 'root logger', root_logger, id(root_logger) 
root_logger = logging.root 
print 'root logger', root_logger, id(root_logger)

#结果
root logger <logging.RootLogger object at 0x7f7aa0fdd6d0> 140164663727824
root logger <logging.RootLogger object at 0x7f7aa0fdd6d0> 140164663727824
# 他们是同一个root logger
import logging
# 没有创建logger, 默认是root logger, 直接打印在屏幕
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(name)s:%(levelname)s:%(message)s')
# logging.warning('this is warning')
# logging.info('this is info')

                                                                                                       
logger = logging.getLogger('apps')
apps_handler = logging.FileHandler(filename="apps.log")
logger.addHandler(apps_handler)
logger.setLevel(logging.DEBUG)
logger.info('shis')
print logger.handlers
print logger

logging.basicConfig
Does basic configuration for the logging system by creating a StreamHandler with a default Formatter and adding it to the root logger.

上面流程讲解:

  • 首先 logging.basicConfig 配置的是root logger 的StreamHandler 的格式,即打印在终端的内容
  • 然后新的apps logger 都是root logger的子,输出的时候,同时会输出到root logger, 除非 logger.propagate = False。
  • 所以如果没有 logging.basicConfig这个对root logger的配置,app logger就会只发送内容到自己的handlers

logging 通过配置文件配置

配置文件的格式要按照文档的来:https://docs.python.org/2/library/logging.config.html#logging-config-dictschema

PATTERN = {
    'version': 1,
    'formatters': {
        'normal': {
            'format': '%(name)s %(asctime)s %(levelname)s %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S'
        },
        'raw': {
            'format': '%(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',
            'formatter': 'normal',
        },
        'root': {
            'class': 'logging.handlers.WatchedFileHandler',
            'formatter': 'normal',
            'filename': RUNTIME_HOME + '/var/log/root.log',
            'mode': 'a',
            'level': 'INFO',
        },
        'extapi': {
            'class': 'logging.handlers.WatchedFileHandler',
            'formatter': 'normal',
            'filename': RUNTIME_HOME + '/var/log/ext_api.log',
            'mode': 'a',
            'level': 'DEBUG',
        },
        'api': {
            'class': 'logging.handlers.WatchedFileHandler',
            'formatter': 'normal',
            'filename': RUNTIME_HOME + '/var/log/api.log',
            'mode': 'a',
            'level': 'DEBUG',
        },
    },
    'loggers': {
        'API': {'level': 'DEBUG',
                'handlers': ['api'],
                },
        'EXTAPI': {'level': 'DEBUG',
                   'handlers': ['extapi'],
                   },
        'requests.packages.urllib3.connectionpool': {'level': 'ERROR'},
    },
    'root': {
        'handlers': ['root',],
        'level': 'INFO',
    }
}

初始化logging

logging.config.dictConfig(log_config.PATTERN)

这样就能从配置文件初始化好所有的logger, 而不用通过addHandlers, 等方法动态修改logger
调用logger写日志

# 没有定义的logger
tool_logger = logging.getLogger('ToolApi')
# 由于没有定义,会冒泡到root logger
tool_logger.info('----------------')
# 定义过的api logger
api_logger = logging.getLogger('API')
# 定义过了,所以api自己的handler会处理,同时会冒泡到root logger处理
api_logger.error('fuck !')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容