1. 程式人生 > >Python自動化運維之常用模塊—logging

Python自動化運維之常用模塊—logging

logging python 常用模塊 日誌模塊

在現實生活中,記錄日誌非常重要。銀行轉賬時會有轉賬記錄;如果有出現什麽問題,人們可以通過日誌數據來搞清楚到底發生了什麽。
對於系統開發、調試以及運行,記錄日誌都是同樣的重要。如果沒有日誌記錄,程序崩潰時你幾乎就沒辦法弄明白到底發生了什麽事情。
1、簡單使用

import logging
logging.debug(‘debug message‘)
logging.info(‘info message‘)
logging.warn(‘warn message‘)
logging.error(‘error message‘)
logging.critical(‘critical message‘)

執行結果:

WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

默認情況下,logging模塊將日誌打印到屏幕上(stdout),日誌級別為WARNING(即只有日誌級別高於WARNING的日誌信息才會輸出),日誌格式如下圖所示:
1.1 日誌級別

級別

數字形式

何時使用

DEBUG

10

詳細信息,典型地調試問題時會感興趣。

INFO

20

證明事情按預期工作。

WARNING

30

表明發生了一些意外,或者不久的將來會發生問題(如‘磁盤滿了’)。軟件還是在正常工作。

ERROR

40

由於更嚴重的問題

CRITICAL

50

嚴重錯誤,表明軟件已,軟件已不能執行一些功能了。不能繼續運行了。

NOSET

0

getattr(日誌、記錄級。upper()

1.2 簡單配置

import logging
# 通過下面的方式進行簡單配置輸出方式與日誌級別
logging.basicConfig(filename=‘logger.log‘, level=logging.INFO)
logging.debug(‘debug message‘)
logging.info(‘info message‘)
logging.warn(‘warn message‘)
logging.error(‘error message‘)
logging.critical(‘critical message‘)

執行結果:

標準輸出(屏幕)未顯示任何信息,發現當前工作目錄下生成了logger.log,內容如下:

INFO:root:info message
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

因為通過level=logging.INFO設置日誌級別為INFO,所以所有的日誌信息均輸出出來了。

2、重要的概念
Logger 記錄器,暴露了應用程序代碼能直接使用的接口。
Handler 處理器,將(記錄器產生的)日誌記錄發送至合適的目的地。
Filter 過濾器,提供了更好的粒度控制,它可以決定輸出哪些日誌記錄。
Formatter 格式化器,指明了最終輸出中日誌記錄的布局。
1.2.1 Logger 記錄器
Logger是一個樹形層級結構,在使用接口debug,info,warn,error,critical之前必須創建Logger實例,即創建一個記錄器,如果沒有顯式的進行創建,則默認創建一個root logger,並應用默認的日誌級別(WARN),處理器Handler(StreamHandler,即將日誌信息打印輸出在標準輸出上),和格式化器Formatter(默認的格式即為第一個簡單使用程序中輸出的格式)。

創建方法: logger = logging.getLogger(logger_name)

創建Logger實例後,可以使用以下方法進行日誌級別設置,增加處理器Handler。
logger.setLevel(logging.ERROR) # 設置日誌級別為ERROR,即只有日誌級別大於等於ERROR的日誌才會輸出

logger.addHandler(handler_name) # 為Logger實例增加一個處理器
logger.removeHandler(handler_name) # 為Logger實例刪除一個處理器

2.2 Handler 處理器
Handler處理器類型有很多種,比較常用的有三個,StreamHandler,FileHandler,NullHandler,詳情可以訪問Python logging.handlers
創建StreamHandler之後,可以通過使用以下方法設置日誌級別,設置格式化器Formatter,增加或刪除過濾器Filter。

ch.setLevel(logging.WARN) # 指定日誌級別,低於WARN級別的日誌將被忽略
ch.setFormatter(formatter_name) # 設置一個格式化器formatter
ch.addFilter(filter_name) # 增加一個過濾器,可以增加多個
ch.removeFilter(filter_name) # 刪除一個過濾器

StreamHandler

創建方法: sh = logging.StreamHandler(stream=None)

FileHandler

創建方法: fh = logging.FileHandler(filename, mode=‘a‘, encoding=None, delay=False)

NullHandler
NullHandler類位於核心logging包,不做任何的格式化或者輸出。本質上它是個“什麽都不做”的handler,由庫開發者使用。
2.3 Formatter 格式化器
使用Formatter對象設置日誌信息最後的規則、結構和內容,默認的時間格式為%Y-%m-%d %H:%M:%S。

創建方法: formatter = logging.Formatter(fmt=None, datefmt=None)

其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,將使用‘%(message)s‘。如果不指明datefmt,將使用ISO8601日期格式。
2.4 Filter 過濾器
Handlers和Loggers可以使用Filters來完成比級別更復雜的過濾。Filter基類只允許特定Logger層次以下的事件。例如用‘A.B’初始化的Filter允許Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等記錄的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串來初始化,所有的事件都接受。

創建方法: filter = logging.Filter(name=‘‘)
  • 以下是相關概念總結:
    熟悉了這些概念之後,有另外一個比較重要的事情必須清楚,即Logger是一個樹形層級結構;

  • Logger可以包含一個或多個Handler和Filter,即Logger與Handler或Fitler是一對多的關系;

  • 一個Logger實例可以新增多個Handler,一個Handler可以新增多個格式化器或多個過濾器,而且日誌級別將會繼承。


3、Logging工作流程

  • 第一次導入logging模塊或使用reload函數重新導入logging模塊,logging模塊中的代碼將被執行,這個過程中將產生logging日誌系統的默認配置。

  • 自定義配置(可選)。logging標準模塊支持三種配置方式: dictConfig,fileConfig,listen。其中,dictConfig是通過一個字典進行配置Logger,Handler,Filter,Formatter;fileConfig則是通過一個文件進行配置;而listen則監聽一個網絡端口,通過接收網絡數據來進行配置。當然,除了以上集體化配置外,也可以直接調用Logger,Handler等對象中的方法在代碼中來顯式配置。

  • 使用logging模塊的全局作用域中的getLogger函數來得到一個Logger對象實例(其參數即是一個字符串,表示Logger對象實例的名字,即通過該名字來得到相應的Logger對象實例)。

  • 使用Logger對象中的debug,info,error,warn,critical等方法記錄日誌信息。

4、logging模塊使用過程
4.1 logging模塊處理流程

  • 判斷日誌的等級是否大於Logger對象的等級,如果大於,則往下執行,否則,流程結束。

  • 產生日誌。第一步,判斷是否有異常,如果有,則添加異常信息。第二步,處理日誌記錄方法(如debug,info等)中的占位符,即一般的字符串格式化處理。

  • 使用註冊到Logger對象中的Filters進行過濾。如果有多個過濾器,則依次過濾;只要有一個過濾器返回假,則過濾結束,且該日誌信息將丟棄,不再處理,而處理流程也至此結束。否則,處理流程往下執行。

  • 在當前Logger對象中查找Handlers,如果找不到任何Handler,則往上到該Logger對象的父Logger中查找;如果找到一個或多個Handler,則依次用Handler來處理日誌信息。但在每個Handler處理日誌信息過程中,會首先判斷日誌信息的等級是否大於該Handler的等級,如果大於,則往下執行(由Logger對象進入Handler對象中),否則,處理流程結束。

  • 執行Handler對象中的filter方法,該方法會依次執行註冊到該Handler對象中的Filter。如果有一個Filter判斷該日誌信息為假,則此後的所有Filter都不再執行,而直接將該日誌信息丟棄,處理流程結束。

  • 使用Formatter類格式化最終的輸出結果。 註:Formatter同上述第2步的字符串格式化不同,它會添加額外的信息,比如日誌產生的時間,產生日誌的源代碼所在的源文件的路徑等等。

  • 真正地輸出日誌信息(到網絡,文件,終端,郵件等)。至於輸出到哪個目的地,由Handler的種類來決定。
    註:以上內容摘抄自第三條參考資料,內容略有改動,轉載特此聲明。

5、日誌配置
5.1 配置方式

  • 顯式創建記錄器Logger、處理器Handler和格式化器Formatter,並進行相關設置;

  • 通過簡單方式進行配置,使用basicConfig()函數直接進行配置;

  • 通過配置文件進行配置,使用fileConfig()函數讀取配置文件;

  • 通過配置字典進行配置,使用dictConfig()函數讀取配置信息;

  • 通過網絡進行配置,使用listen()函數進行網絡配置。


5.2 basicConfig關鍵字參數

關鍵字

描述

filename

創建一個FileHandler,使用指定的文件名,而不是使用StreamHandler。

filemode

如果指明了文件名,指明打開文件的模式(如果沒有指明filemode,默認為‘a‘)。

format

handler使用指明的格式化字符串。

datefmt

使用指明的日期/時間格式。

level

指明根logger的級別。

stream

使用指明的流來初始化StreamHandler。該參數與‘filename‘不兼容,如果兩個都有,‘stream‘被忽略。

5.3 有用的format格式

格式

描述

%(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

用戶輸出的消息

5.4 配置示例

5.4.1 顯式配置

使用程序logger.py如下:

import logging
# create logger
logger_name = "example"
file_log = "accesss.log"
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
# create file handler
fh = logging.FileHandler(file_log)
fh.setLevel(logging.WARN)
# create formatter
fmt = "%(asctime)-15s %(levelname)s %(filename)s %(lineno)d %(process)d %(message)s"
datefmt = "%a %d %b %Y %H:%M:%S"
formatter = logging.Formatter(fmt, datefmt)
# add handler and formatter to logger
fh.setFormatter(formatter)
logger.addHandler(fh)
# print log info
logger.debug(‘debug message‘)
logger.info(‘info message‘)
logger.warn(‘warn message‘)
logger.error(‘error message‘)
logger.critical(‘critical message‘)

5.4.2 文件配置
配置文件logging.conf如下:

[loggers]
keys=root,example01
[logger_root]
level=DEBUG
handlers=hand01,hand02
[logger_example01]
handlers=hand01,hand02
qualname=example01
propagate=0
[handlers]
keys=hand01,hand02
[handler_hand01]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stderr,)
[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form01
args=(‘log.log‘, ‘a‘)
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

使用程序logger.py如下:

import logging
import logging.config
logging.config.fileConfig("logging.conf")
# create logger
logger_name = "example"
logger = logging.getLogger(logger_name)
logger.debug(‘debug message‘)
logger.info(‘info message‘)
logger.warn(‘warn message‘)
logger.error(‘error message‘)
logger.critical(‘critical message‘)

5.4.3 字典配置

import logging
import logging.config
logger = logging.getLogger(__name__)
# load config from file 
# logging.config.fileConfig(‘logging.ini‘, disable_existing_loggers=False)
# or, for dictConfig
logging.config.dictConfig({
    ‘version‘: 1,              
    ‘disable_existing_loggers‘: False,  # this fixes the problem
 
    ‘formatters‘: {
        ‘standard‘: {
            ‘format‘: ‘%(asctime)s [%(levelname)s] %(name)s: %(message)s‘
        },
    },
    ‘handlers‘: {
        ‘default‘: {
            ‘level‘:‘INFO‘,    
            ‘class‘:‘logging.StreamHandler‘,
        },  
    },
    ‘loggers‘: {
        ‘‘: {                  
            ‘handlers‘: [‘default‘],        
            ‘level‘: ‘INFO‘,  
            ‘propagate‘: True  
        }
    }
})
logger.info(‘It works!‘)


5.4.4 監聽配置
可以使用logging.config.listen(port=DEFAULT_LOGGING_CONFIG_PORT)進行完善本文。

5.4.5 JSON配置
配置文件logging.json如下:

{
    "version": 1,
    "disable_existing_loggers": false,
    "formatters": {
        "simple": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(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": "info.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        },
 
        "error_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "ERROR",
            "formatter": "simple",
            "filename": "errors.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        }
    },
 
    "loggers": {
        "my_module": {
            "level": "ERROR",
            "handlers": ["console"],
            "propagate": "no"
        }
    },
 
    "root": {
        "level": "INFO",
        "handlers": ["console", "info_file_handler", "error_file_handler"]
    }
}

使用程序logger.py如下:

import json
import logging.config
 
def setup_logging(
    default_path=‘logging.json‘, 
    default_level=logging.INFO,
    env_key=‘LOG_CFG‘
):
    """Setup logging configuration
 
    """
    path = default_path
    value = os.getenv(env_key, None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path, ‘rt‘) as f:
            config = json.load(f)
        logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)

5.4.6 YAML配置
配置文件logging.yaml如下:

 ---
version: 1
disable_existing_loggers: False
formatters:
    simple:
        format: "%(asctime)s - %(name)s - %(levelname)s - %(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: info.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8
    error_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: ERROR            
        formatter: simple
        filename: errors.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8
loggers:
    my_module:
        level: ERROR
        handlers: [console]
        propagate: no
root:
    level: INFO
    handlers: [console, info_file_handler, error_file_handler]
...

使用程序logger.py如下:

import os
import logging.config
import yaml
def setup_logging(
    default_path=‘logging.yaml‘, 
    default_level=logging.INFO,
    env_key=‘LOG_CFG‘
):
    """Setup logging configuration
    """
    path = default_path
    value = os.getenv(env_key, None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path, ‘rt‘) as f:
            config = yaml.load(f.read())
        logging.config.dictConfig(config)
    else:
        lo

6、使用 __name__ 作為 logger 的名稱
雖然不是非得將 logger 的名稱設置為 __name__ ,但是這樣做會給我們帶來諸多益處。在 python 中,變量 __name__ 的名稱就是當前模塊的名稱。比如,在模塊 “foo.bar.my_module” 中調用 logger.getLogger(__name__) 等價於調用logger.getLogger(“foo.bar.my_module”) 。當你需要配置 logger 時,你可以配置到 “foo” 中,這樣包 foo 中的所有模塊都會使用相同的配置。當你在讀日誌文件的時候,你就能夠明白消息到底來自於哪一個模塊。
7、捕捉異常並使用 traceback 記錄它
出問題的時候記錄下來是個好習慣,但是如果沒有 traceback ,那麽它一點兒用也沒有。你應該捕獲異常並用 traceback 把它們記錄下來。比如下面這個例子:
使用參數 exc_info=true 調用 logger 方法, traceback 會輸出到 logger 中。你可以看到下面的結果:

try:
    open(‘/path/to/does/not/exist‘, ‘rb‘)
except (SystemExit, KeyboardInterrupt):
    raise
except Exception, e:
    logger.error(‘Failed to open file‘, exc_info=True)

你也可以調用 logger.exception(msg, _args),它等價於 logger.error(msg, exc_info=True, _args)。
千萬不要在模塊層次獲取 Logger,除非 disable_existing_loggers 被設置為 False
你可以看到很多在模塊層次獲取 logger 的例子(在這篇文章我也使用了很多,但這僅僅為了讓示例更短一些)。它們看上去沒什麽壞處,但事實上,這兒是有陷阱的 – 如果你像這樣在模塊中使用 Logger,Python 會保留從文件中讀入配置前所有創建的所有 logger。
my_module.py

import logging
logger = logging.getLogger(__name__)
def foo():
    logger.info(‘Hi, foo‘)
class Bar(object):
    def bar(self):
        logger.info(‘Hi, bar‘)
main.py 
import logging
 
logger = logging.getLogger(__name__)
 
def foo():
    logger.info(‘Hi, foo‘)
 
class Bar(object):
    def bar(self):
        logger.info(‘Hi, bar‘)

本應該在日誌中看到記錄,但是你卻什麽也沒有看到。為什麽呢?這就是因為你在模塊層次創建了 logger,然後你又在加載日誌配置文件之前就導入了模塊。logging.fileConfig 與 logging.dictConfig 默認情況下會使得已經存在的 logger 失效。所以,這些配置信息不會應用到你的 Logger 上。你最好只在你需要 logger 的時候才獲得它。反正創建或者取得 logger 的成本很低。你可以這樣寫你的代碼:

import logging
 
def foo():
    logger = logging.getLogger(__name__)
    logger.info(‘Hi, foo‘)
 
class Bar(object):
    def __init__(self, logger=None):
        self.logger = logger or logging.getLogger(__name__)
 
    def bar(self):
        self.logger.info(‘Hi, bar‘)

這樣,logger 就會在你加載配置後才會被創建。這樣配置信息就可以正常應用。
python2.7 之後的版本中 fileConfg 與 dictConfig 都新添加了 “disable_existing_loggers” 參數,將其設置為 False,上面提到的問題就可以解決了。例如:

8、使用旋轉文件句柄
如果你用 FileHandler 寫日誌,文件的大小會隨著時間推移而不斷增大。最終有一天它會占滿你所有的磁盤空間。為了避免這種情況出現,你可以在你的生成環境中使用 RotatingFileHandler 替代 FileHandler。
9、如果你有多個服務器可以啟用一個專用的日誌服務器
當你有多個服務器和不同的日誌文件時,你可以創建一個集中式的日誌系統來收集重要的(大多數情況是警告或者錯誤消息)信息。然後通過監測這些日誌信息,你就可以很容易地發現系統中的問題了。
10、總結
Python 的日誌庫設計得如此之好,真是讓人欣慰,我覺得這是標準庫中最好的一部分了,你不得不選擇它。它很靈活,你可以用你自己的 handler 或者 filter。已經有很多的第三方的 handler 了,比如 pyzmq 提供的 ZeroMQ 日誌句柄,它允許你通過 zmq 套接字發送日誌消息。如果你還不知道怎麽正確的使用日誌系統,這篇文章將會非常有用。有了很好的日誌記錄實踐,你就能非常容易地發現系統中的問題。這是很非常值得投資的。:)
日誌模塊使用總結:

    1、加載logging模塊
    2、創建一個logger,並設置service用戶記錄日誌
    logger = logging.getLogger("service"),使用%(name)s記錄
    3、設置logger級別
    logger.setLevel(logging.DEBUG)
    4、創建一個handler,日誌流向(文件或控制臺,默認是控制臺)
    # 創建一個handler,用於寫入日誌文件
    fh = logging.FileHandler(‘access.log‘)
    # 再創建一個handler,用於輸出到控制臺
    ch = logging.StreamHandler()
    5、# 定義handler的輸出格式formatter
    formatter = logging.Formatter(‘%(asctime)s - %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s - %(message)s‘)
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    6、給logger添加handler
    logger.addHandler(fh)
    logger.addHandler(ch)
    7、記錄一條日誌
    logger.debug(‘logger debug message‘)
    logger.info(‘logger info message‘)
    logger.warning(‘logger warning message‘)
    logger.error(‘logger error message‘)
    logger.critical(‘logger critical message‘)


本文出自 “炫維” 博客,請務必保留此出處http://xuanwei.blog.51cto.com/11489734/1954983

Python自動化運維之常用模塊—logging