Python 日志Logging詳解

Logging詳解

日志按照下面四個層次來完成日志的功能

  • Loggers expose the interface that application code directly uses.
    Logger暴露出來給應(yīng)用使用的接口
  • Handlers send the log records (created by loggers) to the appropriate destination.
    Handlers是發(fā)送日志記錄(由logger創(chuàng)建的)到合適的目的地鸠信,包括文件灵巧,屏幕,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
# 沒有創(chuàng)建logger, 默認是root logger叉庐, 直接打印在屏幕
# root logger 默認的level 是warning螟蝙,所以這里設(shè)置成debug魔招,才能打印info的日志
# 設(shè)置了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
"""

# 創(chuàng)建一個新的apps 的logger刻两, 如果logger不設(shè)置,就會用root logger那套(打印到屏幕和上面的格式)
# 因為它是會默認傳播到祖先logger
logger = logging.getLogger('apps')
logger.setLevel(logging.DEBUG)
# 是否傳播這個日志到祖先logger, 如果設(shè)置了False 就不會傳到root logger(祖先Logger)的
# 默認StreamHandler那里滴某, 也就是不會打印在頁面上
logger.propagate = False
# 添加handler, 決定日志落地到哪里磅摹,可以多個
# 這個是記錄在文件的Handler
apps_handler = logging.FileHandler(filename="apps.log")
# 設(shè)置這個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會出現(xiàn)這個日志户誓。
# 這里充分說明logger的繼承關(guān)系

logging日志流程


logging_flow.png
tail apps.log
# apps ,apps.owan 的logger都把日志寫到apps.log了, 而且格式一樣
# 因為child_logger沒有設(shè)置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

# 直接繼承l(wèi)ogging.Logger 那么就是說這個類就是一個Logger幕侠, 有了Logger所有方法
# 只是在類里面添加一些內(nèi)部方法帝美,讓logger 封裝addhandler, setformatter等方法
class LogHandler(logging.Logger):
    # 單例模式
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            # 一開始居然用了 cls()來實例化 導(dǎo)致無限次調(diào)用
            # 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))
        # 設(shè)置日志回滾, 保存在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)

#結(jié)果
root logger <logging.RootLogger object at 0x7f7aa0fdd6d0> 140164663727824
root logger <logging.RootLogger object at 0x7f7aa0fdd6d0> 140164663727824
# 他們是同一個root logger
import logging
# 沒有創(chuàng)建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 的格式窗骑,即打印在終端的內(nèi)容
  • 然后新的apps logger 都是root logger的子女责,輸出的時候,同時會輸出到root logger, 除非 logger.propagate = False创译。
  • 所以如果沒有 logging.basicConfig這個對root logger的配置抵知,app logger就會只發(fā)送內(nèi)容到自己的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, 等方法動態(tài)修改logger
調(diào)用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 !')
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末立砸,一起剝皮案震驚了整個濱河市掖疮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颗祝,老刑警劉巖浊闪,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異螺戳,居然都是意外死亡搁宾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門倔幼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盖腿,“玉大人,你說我怎么就攤上這事◆娓” “怎么了鸟款?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茂卦。 經(jīng)常有香客問我欠雌,道長,這世上最難降的妖魔是什么疙筹? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任富俄,我火速辦了婚禮,結(jié)果婚禮上而咆,老公的妹妹穿的比我還像新娘霍比。我一直安慰自己,他們只是感情好暴备,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布悠瞬。 她就那樣靜靜地躺著,像睡著了一般涯捻。 火紅的嫁衣襯著肌膚如雪浅妆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天障癌,我揣著相機與錄音凌外,去河邊找鬼。 笑死涛浙,一個胖子當著我的面吹牛康辑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轿亮,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼疮薇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了我注?” 一聲冷哼從身側(cè)響起按咒,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎但骨,沒想到半個月后励七,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嗽冒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年呀伙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片添坊。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖箫锤,靈堂內(nèi)的尸體忽然破棺而出贬蛙,到底是詐尸還是另有隱情雨女,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布阳准,位于F島的核電站氛堕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏野蝇。R本人自食惡果不足惜讼稚,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绕沈。 院中可真熱鬧锐想,春花似錦、人聲如沸乍狐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浅蚪。三九已至藕帜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惜傲,已是汗流浹背洽故。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盗誊,地道東北人收津。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像浊伙,于是被迫代替她去往敵國和親撞秋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容