細說 Python logging

(可在我的博客閱讀更多文章)

最近有個需求是把以前字符串輸出的log 改為json 格式,看了別人的例子,還是有些比較茫然,索性就把logging 整個翻了一邊,做點小總結.

初看log

在程序中, log 的用處寫代碼的你用你知道,log 有等級,DEBUG, INFO,...之類,還會記錄時間,log 發(fā)生的位置,在Python 中用的多的就是logging 這個標準庫中的包了.當打log 的時候究竟發(fā)生了什么? 是如何把不同級別的log 輸出到不同文件里,還能在控制臺輸出.......

最簡單的用法

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

1,第一行導入包
2,第二行利用basicConfig 對輸出的格式,和輸出級別做了限制
3, 后面分別輸出了三條不同級別的 log

Logging Levels

Level Numeric value
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

共有幾個等級, 每個等級對應一個Int 型整數(shù) ,每個等級都會有一個方法與之對應,這樣輸出的內容就有了不同的等級.

logger 流程,

整個過程,還是不是很詳細,貼個圖吧, 現(xiàn)在看還太早,也說不清真?zhèn)€過程到底發(fā)生了什么,先放著,回頭來看會比較好懂.


loger flow
loger flow

讀代碼

代碼結構

logging 在源碼中有三個文件,結構如下:

├── config.py
├── handlers.py
└── __init__.py

_int.py中實現(xiàn)了基礎功能,主要的邏輯就在這個文件中
handlers.py 是一些Handlers (用處后面會明白)用起來很方便的.
config.py 是對配置做處理的方法.

objects

LogRecord Objects

每一次log 都會實例化一個Record 對象岳枷,這個對象有很多屬性,最后對LogRecord 做一下format 就輸出了氛驮,格式化的log 俄讹,里面就基本就是這個對象的屬性了蒋譬。

class LogRecord(object):
    def __init__(self, name, level, pathname, lineno,
                 msg, args, exc_info, func=None):
        ct = time.time()
        self.name = name
        self.msg = msg
        if (args and len(args) == 1 and isinstance(args[0], collections.Mapping)
            and args[0]):
            args = args[0]
        self.args = args
        self.levelname = getLevelName(level)
        self.levelno = level
        self.pathname = pathname
        try:
            self.filename = os.path.basename(pathname)
            self.module = os.path.splitext(self.filename)[0]
        except (TypeError, ValueError, AttributeError):
            self.filename = pathname
            self.module = "Unknown module"
        self.exc_info = exc_info
        self.exc_text = None      # used to cache the traceback text
        self.lineno = lineno
        self.funcName = func
        self.created = ct
        self.msecs = (ct - long(ct)) * 1000
        self.relativeCreated = (self.created - _startTime) * 1000
        if logThreads and thread:
            self.thread = thread.get_ident()
            self.threadName = threading.current_thread().name
        else:
            self.thread = None
            self.threadName = None
        if not logMultiprocessing:
            self.processName = None
        else:
            self.processName = 'MainProcess'
            mp = sys.modules.get('multiprocessing')
            if mp is not None:
                try:
                    self.processName = mp.current_process().name
                except StandardError:
                    pass
        if logProcesses and hasattr(os, 'getpid'):
            self.process = os.getpid()
        else:
            self.process = None

    def __str__(self):
        return '<LogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno,
            self.pathname, self.lineno, self.msg)

    def getMessage(self):
         pass

看代碼就發(fā)現(xiàn), 這個類沒做什么事情尤蒿,就是一個model 而已, 有一個得到msg 的方法

Formatter Objects

Formatter 就是對Record 專門格式化的對象吗氏,它有一個format 方法,我們實現(xiàn)這個方法就能 做到不同的輸出揽乱,我的需求是做json 格式的log 其實關鍵就在寫一個Formatter 就好了


class Formatter(object):
    converter = time.localtime

    def __init__(self, fmt=None, datefmt=None):
        if fmt:
            self._fmt = fmt
        else:
            self._fmt = "%(message)s"
        self.datefmt = datefmt

    def formatTime(self, record, datefmt=None):
        pass


    def formatException(self, ei):
       pass

    def usesTime(self):
        return self._fmt.find("%(asctime)") >= 0

    def format(self, record):
       pass

刪掉源代碼中的實現(xiàn)細節(jié)名眉,這個類里面主要的是format 方法,這是默認最基本的Formater 锤窑,還有專門對exception 璧针,時間做格式化的方法。具體是哪個渊啰,看方法名就很清楚了探橱,具體每個方法怎么實現(xiàn)的,一眼也就懂了绘证。fmt 是制定格式化的隧膏,具體怎么指定在最基礎的用法中就有例子,datefmt 是對時間格式的指定嚷那。

Filter Objects

這個類是Logger 和Handler 的基類胞枕,主要有一個Filter 方法,和一個filters 屬性

Handler Objects

叫Handler 的類還真的不少,在SocketServer 中也有看到,具體的功能都在Handler 中.在這里,組合所有的Formatter ,和控制log 的輸出的方向魏宽,繼承自Filter.

 def __init__(self, level=NOTSET):
        Filterer.__init__(self)
        self._name = None
        self.level = _checkLevel(level)
        self.formatter = None
        _addHandlerRef(self)
        self.createLock()

init方法中看到腐泻,Handler 也有一個屬性,通過把自身的屬性和LogRecord 的level對比來決定是否處理這個LogRecord 的队询。每個Handler 都有一個Formatter 屬性派桩,其實就是上面介紹的Formatter 。Handler 就是來控制LogRecord 和Formatter 的蚌斩,它還可以控制輸出的方式铆惑,在后面會有,StreamHandler,FileHandler等送膳。通過名稱也就能明白具體能干什么员魏,這就是編程取名的智慧。

Logger Objects

這個類通常會通過getLogger()或者getLogger(name)來得到,不會直接new 一個出來.它會有*info(msg, args, kwargs) ,warn(msg, *args, kwargs)等方法,

    def __init__(self, name, level=NOTSET):
        Filterer.__init__(self)
        self.name = name
        self.level = _checkLevel(level)
        self.parent = Noneou
        self.handlers = []
        self.disabled = 0

init方法中能看到handlers 屬性,這是一個list 叠聋,每個LogRecord 通過Handlers 不同的handlers 就能以不同的格式輸出到不同的地方了撕阎。每個Logger 可以通過addHandler(hdlr)方法來添加各種Handler,
知道這些你就基本可以隨意定制化了
下面就是我實現(xiàn)的json 格式的Formater,支持控制臺顏色變化碌补,當然前提是你的控制終端支持(Ubuntu14.04測試通過)


import re
import logging
import socket
import json
import traceback
import datetime
import time

try:
    from collections import OrderedDict
except ImportError:
    pass


RESERVED_ATTRS = (
    'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename',
    'funcName', 'levelname', 'levelno', 'lineno', 'module',
    'msecs', 'message', 'msg', 'name', 'pathname', 'process',
    'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName')


RESERVED_ATTR_HASH = dict(zip(RESERVED_ATTRS, RESERVED_ATTRS))

COLORS ={
    'HEADER' : '\\033[95m',
    'INFO' : '\\033[94m',
    'DEBUG' : '\\033[92m',
    'WARNING' : '\\033[93m',
    'ERROR' : '\\033[91m',
    'ENDC' : '\\033[0m',
}

def merge_record_extra(record, target, reserved=RESERVED_ATTR_HASH):
    for key, value in record.__dict__.items():
        if (key not in reserved
            and not (hasattr(key, "startswith")
                     and key.startswith('_'))):
            target[key] = value
    return target

def get_host_info():
    host_name = ''
    local_ip = ''
    try:
        host_name = socket.gethostname()
        local_ip = socket.gethostbyname(host_name)
    except Exception, e:
        pass

    return host_name, local_ip

class JsonFormatterBase(logging.Formatter):

    def __init__(self,  *args, **kwargs):

        logging.Formatter.__init__(self, *args, **kwargs)
        self._required_fields = self.parse()
        self._skip_fields = dict(zip(self._required_fields,self._required_fields))
        self._skip_fields.update(RESERVED_ATTR_HASH)
    def parse(self):
        standard_formatters = re.compile(r'\\((.+?)\\)', re.IGNORECASE)
        return standard_formatters.findall(self._fmt)


    def add_fields(self, record ):
        log_record = {}

        for field in self._required_fields:
            log_record[field] = record.__dict__.get(field)

        host_name , local_ip = get_host_info()

        log_record[u'@hostName'] = host_name
        log_record[u'@localIp'] = local_ip
        return log_record

        #merge_record_extra(record, log_record, reserved=self._skip_fields)


    def process_log_record(self, log_record):
        """
        Override this method to implement custom logic
        on the possibly ordered dictionary.
        """

        try:
            new_record = OrderedDict()
        except Exception, e:
            return log_record

        key_list = [
            'asctime',
            'levelname',
            '@hostName',
            '@localIp',
            'threadName',
            'thread',
            'name',
            'pathname',
            'lineno',
            'message',
        ]
        for k in key_list:
            new_record[k] = log_record.get(k)
        new_record.update(log_record)
        return new_record

    def jsonify_log_record(self, log_record):
        """Returns a json string of the log record."""

        return json.dumps(log_record, ensure_ascii=False)


    def format_col(self, message_str, level_name):
        """

        是否需要顏色
        """
        return  message_str

    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        if datefmt:
            s = time.strftime(datefmt, ct)
        else:
            t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
            s = "%s.%03d" % (t, record.msecs)
        return s

    def format(self, record):


        if isinstance(record.msg, dict):
            record.message = record.msg

        elif isinstance(record.msg, list) or isinstance(record.msg, tuple):
            record.message = record.msg

        elif isinstance(record.msg, basestring):
            record.message = record.getMessage().split('\\n')

        elif isinstance(record.msg, Exception):
            record.message = traceback.format_exc(record.msg).split('\\n')

        else :
            record.message = repr(record.msg)

        if "asctime" in self._required_fields:
            record.asctime = self.formatTime(record, self.datefmt)

        #
        # if record.exc_info and not message_dict.get('exc_info'):
        #     message_dict['message'] = traceback.format_exception(*record.exc_info)
        log_record = self.add_fields(record)
        log_record = self.process_log_record(log_record)
        message_str  = self.jsonify_log_record(log_record)
        message_str = self.format_col(message_str, level_name=record.levelname)
        return message_str



class ConsoleFormater(JsonFormatterBase):

    def __init__(self, *args, **kwargs):
        JsonFormatterBase.__init__(self, *args, **kwargs)

    def format_col(self, message_str, level_name):
        if level_name in COLORS.keys():
            message_str = COLORS.get(level_name) + message_str + COLORS.get('ENDC')
        return message_str

    def jsonify_log_record(self, log_record):
        return json.dumps(log_record, ensure_ascii=False, indent=4)


class JsonFileFormater(JsonFormatterBase):

    def __init__(self, *args, **kewars):
        JsonFormatterBase.__init__(self, *args, **kewars)

    def jsonify_log_record(self, log_record):
        return json.dumps(log_record, ensure_ascii=False)

配置

很多時候我們并不是這樣自己去實現(xiàn)一些Handler 闻书,F(xiàn)ormater ,之類的代碼名斟,用logging 提供的config 就能做到了,如何寫config下面舉個例子解釋下魄眉,


SC_LOGGING_CONF = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "simple": {
            "format": "%(asctime)s [%(levelname)s] [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] - %(message)s"
        }
    },

    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "formatter": "simple",
            "stream": "ext://sys.stdout"
        },
        "info_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "INFO",
            "formatter": "simple",
            "filename": PATH + "info-" + date.today().isoformat() + ".log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        },
        "error_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "ERROR",
            "formatter": "simple",
            "filename": PATH + "errors-" + date.today().isoformat() + ".log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        }
    },
        "": {
            "level": "INFO",
            "handlers": ["console", "info_file_handler", "error_file_handler"]
        }
    }
}

首先定義了一個formater 叫simaple , 然后定義了三個Handler ,分別是輸出到控制臺,輸出到文件和info,error的闷袒。

 logging.config.dictConfig(CONFIG.SC_LOGGING_CONF)

通過這句就能讓這些配置產生效果了坑律,這也是config.py做的事情,不需要寫很多代碼也能定制個性化的log.囊骤。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末晃择,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子也物,更是在濱河造成了極大的恐慌宫屠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滑蚯,死亡現(xiàn)場離奇詭異浪蹂,居然都是意外死亡,警方通過查閱死者的電腦和手機告材,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門坤次,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人斥赋,你說我怎么就攤上這事缰猴。” “怎么了疤剑?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵滑绒,是天一觀的道長。 經常有香客問我隘膘,道長疑故,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任棘幸,我火速辦了婚禮焰扳,結果婚禮上,老公的妹妹穿的比我還像新娘误续。我一直安慰自己吨悍,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布蹋嵌。 她就那樣靜靜地躺著育瓜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栽烂。 梳的紋絲不亂的頭發(fā)上躏仇,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天恋脚,我揣著相機與錄音,去河邊找鬼焰手。 笑死糟描,一個胖子當著我的面吹牛,可吹牛的內容都是我干的书妻。 我是一名探鬼主播船响,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躲履!你這毒婦竟也來了见间?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤工猜,失蹤者是張志新(化名)和其女友劉穎米诉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篷帅,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡史侣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了犹褒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抵窒。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叠骑,靈堂內的尸體忽然破棺而出李皇,到底是詐尸還是另有隱情,我是刑警寧澤宙枷,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布掉房,位于F島的核電站,受9級特大地震影響慰丛,放射性物質發(fā)生泄漏卓囚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一诅病、第九天 我趴在偏房一處隱蔽的房頂上張望哪亿。 院中可真熱鬧,春花似錦贤笆、人聲如沸蝇棉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篡殷。三九已至,卻和暖如春埋涧,著一層夾襖步出監(jiān)牢的瞬間板辽,已是汗流浹背奇瘦。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劲弦,地道東北人耳标。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像邑跪,于是被迫代替她去往敵國和親麻捻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容

  • 本文章是我大概三年前,在上家單位使用 Python 工作時結合官方文檔做的整理≈5現(xiàn)在 Python 官方文檔聽說已...
    好吃的野菜閱讀 216,899評論 14 232
  • Python logging 模塊 參考 http://blog.csdn.net/zyz511919766/ar...
    ktide閱讀 895評論 0 2
  • 本文翻譯自logging howto 基礎教程 日志是跟蹤軟件運行時發(fā)生事件的一種手段夜赵。Python開發(fā)者在代碼中...
    大蟒傳奇閱讀 4,254評論 0 17
  • Logging框架主要作用是Python里面處理日志 一.logging模塊的組成 loggers :提供應用程...
    YichenWong閱讀 1,568評論 1 0
  • 我結了婚,有了一個可愛的女兒乡革。 我刪了一些過往寇僧,仿佛從未發(fā)生過。曾以為從此穩(wěn)穩(wěn)地就幸福了沸版,而生活卻如此的不堪一擊嘁傀,...
    李乙閱讀 253評論 0 0