1. 程式人生 > 程式設計 >詳解python logging日誌傳輸

詳解python logging日誌傳輸

1.生成日誌並通過http傳輸出去(通過HTTPHandler方式):

#生成併發送日誌
import logging
from logging.handlers import HTTPHandler
import logging.config

def save():
  logger = logging.getLogger(__name__)
  # 生成一個log例項,如果括號為空則返回root logger
  hh = HTTPHandler(host='127.0.0.1:5000',url='/log',method='POST')
  #用HTTPHandler直接傳送日誌,而並不是寫檔案再傳檔案。
  logger.setLevel(logging.INFO)
  #設定日誌最低輸出級別為info
  logger.addHandler(hh)
  #新增Handler物件給記錄器(為logger新增的日誌處理器,可以自定義日誌處理器讓其輸出到其他地方)
  logger.info('存入600元') # 輸出日誌,內容為‘存入600元'
save()

2.用flask接收傳過來的日誌:

#flask接收日誌
from flask import Flask,request
app = Flask(__name__)



@app.route('/log',methods=['POST'])
def say_hello():
  #檢視傳過來的資料格式:
  print(request.mimetype)
  #HTTPHandler傳過來的是個form表單
  print(request.form.to_dict())
  return '<h1>Hello,Flask!</h1>'


if __name__ == '__main__':
  app.run()


輸出:
#這是傳過來的資料格式,是個form表單
application/x-www-form-urlencoded 

#這是form.to_dict解析出來的內容(直接解析成了字典)
{'name': '__main__','msg': '存入600元','args': '()','levelname': 'INFO','levelno': '20','pathname': 'C:/Users/huawei/Desktop/code/log/send_log.py','filename': 'send_log.py','module': 'send_log','exc_info': 'None','exc_text': 'None','stack_info': 'None','lineno': '15','funcName': 'save','created': '1593581146.172768','msecs': '172.76811599731445','relativeCreated': '176.5270233154297','thread': '13904','threadName': 'MainThread','processName': 'MainProcess','process': '3656'}

3.logging模組介紹

Python的logging模組提供了通用的日誌系統,熟練使用logging模組可以方便開發者開發第三方模組或者是自己的Python應用。同樣這個模組提供不同的日誌級別,並可以採用不同的方式記錄日誌,比如檔案,HTTP、GET/POST,SMTP,Socket等,甚至可以自己實現具體的日誌記錄方式。下文我將主要介紹如何使用檔案方式記錄log。

1.基本概念:

logging模組包括logger,handler,filter,formatter這四個基本概念。

logging模組與log4j的機制是一樣的,只是具體的實現細節不同。模組提供logger,handler,filter,formatter。

  • logger:提供日誌介面,供應用程式碼使用。logger最長用的操作有兩類:配置和傳送日誌訊息。可以通過logging.getLogger(name)獲取logger物件,如果不指定name則返回root物件,多次使用相同的name呼叫getLogger方法返回同一個logger物件。
  • handler:將日誌記錄(log record)傳送到合適的目的地(destination),比如檔案,socket等。一個logger物件可以通過addHandler方法新增0到多個handler,每個handler又可以定義不同日誌級別,以實現日誌分級過濾顯示。
  • filter:提供一種優雅的方式決定一個日誌記錄是否傳送到handler。
  • formatter:指定日誌記錄輸出的具體格式。formatter的構造方法需要兩個引數:訊息的格式字串和日期字串,這兩個引數都是可選的。

與log4j類似,logger,handler和日誌訊息的呼叫可以有具體的日誌級別(Level),只有在日誌訊息的級別大於logger和handler的級別。

import logging
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',datefmt='%a,%d %b %Y %H:%M:%S',filename='myapp.log',filemode='w')
#################################################################################################
#定義一個StreamHandler,將INFO級別或更高的日誌資訊列印到標準錯誤,並將其新增到當前的日誌處理物件#
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
#################################################################################################
logging.debug('This is debug message')
logging.info('This is info message')
logging.warning('This is warning message')
 
螢幕上列印:
root : INFO This is info message
root : WARNING This is warning message
./myapp.log檔案中內容為:
Sun,24 May 2009 21:48:54 demo2.py[line:11] DEBUG This is debug message
Sun,24 May 2009 21:48:54 demo2.py[line:12] INFO This is info message
Sun,24 May 2009 21:48:54 demo2.py[line:13] WARNING This is warning message

2.主要用法:

logging.StreamHandler: 日誌輸出到流,可以是sys.stderr、sys.stdout或者檔案

logging.FileHandler: 日誌輸出到檔案

日誌回滾方式,實際使用時用RotatingFileHandler和TimedRotatingFileHandler

logging.handlers.BaseRotatingHandler

logging.handlers.RotatingFileHandler

logging.handlers.TimedRotatingFileHandler

logging.handlers.SocketHandler: 遠端輸出日誌到TCP/IP sockets

logging.handlers.DatagramHandler: 遠端輸出日誌到UDP sockets

logging.handlers.SMTPHandler: 遠端輸出日誌到郵件地址

logging.handlers.SysLogHandler: 日誌輸出到syslog

logging.handlers.NTEventLogHandler: 遠端輸出日誌到Windows NT/2000/XP的事件日誌

logging.handlers.MemoryHandler: 日誌輸出到記憶體中的制定buffer

logging.handlers.HTTPHandler: 通過"GET"或"POST"遠端輸出到HTTP伺服器

舉例:

import logging
import sys
# 獲取logger例項,如果引數為空則返回root logger
logger = logging.getLogger("AppName")
# 指定logger輸出格式
formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s')
# 檔案日誌
file_handler = logging.FileHandler("test.log")
file_handler.setFormatter(formatter) # 可以通過setFormatter指定輸出格式
# 控制檯日誌
console_handler = logging.StreamHandler(sys.stdout)
console_handler.formatter = formatter # 也可以直接給formatter賦值
# 為logger新增的日誌處理器,可以自定義日誌處理器讓其輸出到其他地方
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 指定日誌的最低輸出級別,預設為WARN級別
logger.setLevel(logging.INFO)
# 輸出不同級別的log
logger.debug('this is debug info')
logger.info('this is information')
logger.warn('this is warning message')
logger.error('this is error message')
logger.fatal('this is fatal message,it is same as logger.critical')
logger.critical('this is critical message')
# 2016-10-08 21:59:19,493 INFO : this is information
# 2016-10-08 21:59:19,493 WARNING : this is warning message
# 2016-10-08 21:59:19,493 ERROR : this is error message
# 2016-10-08 21:59:19,493 CRITICAL: this is fatal message,it is same as logger.critical
# 2016-10-08 21:59:19,493 CRITICAL: this is critical message
# 移除一些日誌處理器
logger.removeHandler(file_handler)

4.概述:

python的logging模組(logging是執行緒安全的)給應用程式提供了標準的日誌資訊輸出介面。logging不僅支援把日誌輸出到檔案,還支援把日誌輸出到TCP/UDP伺服器,EMAIL伺服器,HTTP伺服器,UNIX的syslog系統等。在logging中主要有四個概念:logger、handler、filter和formatter,下面會分別介紹。

1.logger

Logger物件扮演了三重角色:

它給應用程式暴漏了幾個方法,以便應用程式能在執行時記錄日誌。

Logger物件根據日誌的級別或根據Filter物件,來決定記錄哪些日誌。

Logger物件負責把日誌資訊傳遞給相關的handler。

在Logger物件中,最常使用的方法分為兩類:configuration,message sending。 configuration方法包括:

setLevel(level)

setLevel(level)方法用來設定logger的日誌級別,如果日誌的級別低於setLevel(level)方法設定的值,那麼logger不會處理它。

logging模組內建的日誌級別有:

CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 (數值越大級別越高)
addFilter(filter)
removeFilter(filter)
addHandler(handler)
removeHandler(handler)

message sending方法包括:

debug(log_message,[*args[,**kwargs]])

使用DEBUG級別,記錄log_message % args。

為了記錄異常資訊,需要將關鍵字引數exc_info設定為一個true值。

logger.debug("Houston,we have a %s","thorny problem",exc_info=1)
info(log_message,**kwargs]])

使用INFO級別,記錄log_message % args。

為了記錄異常資訊,需要將關鍵字引數exc_info設定為一個true值。

logger.info("Houston,"interesting problem",exc_info=1)
warning(log_message,**kwargs]])

使用WARNING級別,記錄log_message % args。

為了記錄異常資訊,需要將關鍵字引數exc_info設定為一個true值。

logger.warning("Houston,"bit of a problem",exc_info=1)
error(log_message,**kwargs]])

使用Error級別,記錄log_message % args。

為了記錄異常資訊,需要將關鍵字引數exc_info設定為一個true值。

logger.error("Houston,"major problem",exc_info=1)
critical(log_message,**kwargs]])

使用CRITICAL級別,記錄log_message % args。

為了記錄異常資訊,需要將關鍵字引數exc_info設定為一個true值。

logger.critical("Houston,"major disaster",exc_info=1)
exception(message[,*args]) 
self.error(*((msg,) + args),**{'exc_info': 1})
log(log_level,log_message,**kwargs]])

使用整型的級別level,記錄log_message % args。

為了記錄異常資訊,需要將關鍵字引數exc_info設定為一個true值。

logger.log(level,"We have a %s","mysterious problem",exc_info=1)
logging.getLogger([name])

方法返回一個Logger例項的引用,如果提供了name引數,那麼它就是這個Logger例項的名稱,如果沒提供name引數,那麼這個Logger例項的名稱是root。

可以通過Logger例項的name屬性,來檢視Logger例項的名稱。

Logger例項的名稱是使用句號(.)分隔的多級結構。

在這種命名方式中,後面的logger是前面的logger的子(父子logger只是簡單的通過命名來識別),比如:有一個名稱為foo的logger,那麼諸如foo.bar、foo.bar.baz和foo.bam這樣的logger都是foo這個logger的子logger。

子logger會自動繼承父logger的定義和配置。

使用相同的名稱多次呼叫logging.getLogger([name])方法,會返回同一個logger物件的引用。

這個規則不僅僅在同一個module有效,而且對在同一個Python直譯器程序的多個module也有效。

因此應用程式可以在一個module中定義一個父logger,然後在其他module中繼承這個logger,而不必把所有的logger都配置一遍

2.handler

handler例項負責把日誌事件分發到具體的目的地。logger物件可以使用addHandler()方法,新增零個或多個handler物件到它自身。一個常見的場景是:應用程式可能希望把所有的日誌都記錄到一個log檔案,所有的ERROR及以上級別的日誌都記錄到stdout,所有的CRITICAL級別的日誌都發送到一個email地址。這個場景需要三個獨立的handler,每個handler負責把特定級別的日誌傳送到特定的地方。

下面是logging模組內建的handler:StreamHandler
FileHandler
RotatingFileHandler
TimedRotatingFileHandler
SocketHandler
DatagramHandler
SysLogHandler
NTEventLogHandler
SMTPHandler
MemoryHandler
HTTPHandler

內建的handler提供了下面的配置方法:

  • setLevel(level)
  • handler物件的setLevel()方法,與logger物件的setLevel()方法一樣,也是用於設定一個日誌級別,如果日誌的級別低於setLevel()方法設定的值,那麼handler不會處理它。
  • setFormatter(formatter)
  • addFilter(filter)
  • removeFilter(filter)

應用程式程式碼不應該直接例項化和使用handler。logging.Handler是一個定義了所有的handler都應該實現的介面和建立了子類能夠使用(或重寫)的一些預設行為的基類。

自定義Handler 自定義的handler必須繼承自logging.Handler,且實現下面的方法:

class Handler(Filterer): 
 def emit(self,record):
 """
 Do whatever it takes to actually log the specified logging record.
 This version is intended to be implemented by subclasses and so
 raises a NotImplementedError.
 """
 raise NotImplementedError,'emit must be implemented '\
     'by Handler subclasses'
 def flush(self):
 """
 Ensure all logging output has been flushed.
 This version does nothing and is intended to be implemented by
 subclasses.
 """
 pass
 def close(self):
 """ 
 Tidy up any resources used by the handler.
 This version does removes the handler from an internal list
 of handlers which is closed when shutdown() is called. Subclasses
 should ensure that this gets called from overridden close()
 methods.
 """
 #get the module data lock,as we're updating a shared structure.
 _acquireLock()
 try: #unlikely to raise an exception,but you never know...
  if self in _handlers:
  del _handlers[self]
  if self in _handlerList:
  _handlerList.remove(self)
 finally:
  _releaseLock()

其中,emit(record)方法負責執行真正地記錄日誌所需的一切事情,在logging.Handler的子類中必須實現這個方法。close()方法負責清理handler所使用的資源(在Python直譯器退出的時候,會呼叫所有的handler的flush()和close()方法),logging.Handler的子類應該確保在重寫close()方法的時候,呼叫父類的該方法。

下面分析logging.StreamHandler的原始碼:

class StreamHandler(Handler): 
 def __init__(self,strm=None):
 Handler.__init__(self)
 if strm is None:
  strm = sys.stderr
 self.stream = strm
 def flush(self):
 if self.stream and hasattr(self.stream,"flush"):
  self.stream.flush()
 def emit(self,record):
 try:
  msg = self.format(record)
  stream = self.stream
  fs = "%s\n"
  if not hasattr(types,"UnicodeType"): #if no unicode support...
  stream.write(fs % msg)
  else:
  try:
   if (isinstance(msg,unicode) and
   getattr(stream,'encoding',None)):
   fs = fs.decode(stream.encoding)
   try:
    stream.write(fs % msg)
   except UnicodeEncodeError:
    #Printing to terminals sometimes fails. For example,#with an encoding of 'cp1251',the above write will
    #work if written to a stream opened or wrapped by
    #the codecs module,but fail when writing to a
    #terminal even when the codepage is set to cp1251.
    #An extra encoding step seems to be needed.
    stream.write((fs % msg).encode(stream.encoding))
   else:
   stream.write(fs % msg)
  except UnicodeError:
   stream.write(fs % msg.encode("UTF-8"))
  self.flush()
 except (KeyboardInterrupt,SystemExit):
  raise
 except:
  self.handleError(record)

在建構函式中,如果提供了strm引數,那麼它就是要輸出到的流,如果沒提供,那麼就會將日誌輸出到標準錯誤輸出流sys.stderr。

  • flush()方法的作用是:重新整理self.stream內部的I/O緩衝區。每次emit日誌之後都會呼叫這個方法,將日誌從I/O緩衝區sync到self.stream。
  • emit(record)方法的作用是:將LogRecord物件(record)記錄到self.stream。emit(record)方法首先呼叫基類logging.Handler提供的format(record)方法,該方法會根據設定的Formatter物件來格式化record物件,得到要記錄的字串msg。然後對fs(fs其實就是在msg的尾部增加一個換行'\n')進行一系列的編碼解碼,將它寫入到self.stream。最後再重新整理self.stream。在emit(record)呼叫期間發生的異常,應該呼叫logging.Handler提供的handleError(record)方法來處理。

3.filter

Filter物件用於對LogRecord物件執行過濾,logger和handler都可以使用filter來過濾record。下面用一個列子來說明Filter基類的作用:

如果使用A.B例項化一個filter,那麼它允許名稱為A.B,A.B.C,A.B.C.D這樣的logger記錄的日誌通過,不允許名稱為A.BB,B.A.B這樣的logger記錄的日誌通過。

如果使用空字串例項化一個filter,那麼它允許所有的事件通過。

Filter基類有一個方法叫filter(record),它用來決定指定的record(LogRecord物件)是否被記錄。如果該方法返回0,則不記錄record;返回非0則記錄record。

Filterer(注意:不是Filter)是logger和handler的基類。它提供了方法來新增和刪除filter,並且提供了filter(record)方法用於過濾record,該方法預設允許record被記錄,但是任何filter都可以否決這個預設行為,如果想要丟棄record,filter(record)方法應該返回0,否則應該返回非0。

4.formatter

Formatter物件用於把一個LogRecord物件轉換成文字,它定義了日誌的格式、結構。與logging.Handler類不同,應用程式可以直接例項化Formatter類,如果需要也可以子類化Formatter,以便定

制一些行為。

Formatter的建構函式接受兩個引數:第一個引數是用於日誌資訊的格式化字串;第二個引數是用於日期的格式化字串。第二個引數可選的,預設值是%Y-%m-%d %H:%M:%S。

日誌資訊的格式化字串用%(<dictionary key>)s風格的字串做替換。

下面是替換字串和它們所代表的含義:

%(name)s
logger的名稱
%(levelno)s
日誌級別的數字表現形式
%(levelname)s
日誌級別的文字表現形式
%(pathname)s
呼叫logging的原始檔的全路徑名
%(filename)s
pathname的檔名部分
%(module)s
模組名(filename的名稱部分)
%(lineno)d
呼叫logging的行號
%(funcName)s
函式名
%(created)f
LogRecord的建立時間(time.time()的返回值)
%(asctime)s
LogRecord的建立時間的文字表現形式
%(msecs)d
建立時間的毫秒部分
%(relativeCreated)d
LogRecord的建立時間,單位是毫秒。這個時間是相對logging模組被載入的時間的(通常就是應用程式啟動的時間)。
%(thread)d
執行緒ID
%(threadName)s
執行緒名稱
%(process)d
程序ID
%(message)s
record.getMessage()的返回結果。

舉例:

配置logging

下面是一個簡單的例子,它會向標準輸出列印日誌:

import logging 
import sys 
logger = logging.getLogger(__name__) 
filter = logging.Filter(__name__) 
formatter = logging.Formatter("%(asctime)s|%(name)-12s|%(message)s","%F %T")
stream_handler = logging.StreamHandler(sys.stdout) 
stream_handler.addFilter(filter) 
stream_handler.setLevel(logging.DEBUG) 
stream_handler.setFormatter(formatter)
logger.setLevel(logging.DEBUG) 
logger.addFilter(filter) 
logger.addHandler(stream_handler)
if __name__ == "__main__": 
 logger.info("info")

執行這個指令碼,輸出結果是:

2015-12-16 13:52:17|__main__ |info

使用配置檔案,配置logging

下面是一個使用配置檔案,配置logging的例子:

import logging 
import logging.config
logging.config.fileConfig("logging.conf")
if __name__ == "__main__": 
 logger = logging.getLogger("test_logging.sublogger")
 logger.info("info")

logging.conf如下:

[loggers]
keys = root,logger
[handlers]
keys = stream_handler
[formatters]
keys = formatter
[logger_root]
handlers = stream_handler
[logger_logger]
handlers = stream_handler 
level = DEBUG 
propagate = 1 
qualname = test_logging
[handler_stream_handler]
class = StreamHandler 
args = (sys.stdout,) 
formatter = formatter 
level = DEBUG
[formatter_formatter]
format = %(asctime)s|%(name)-12s|%(message)s 
datefmt = %F %T

需要解釋的地方有兩處:第一個是logger_xxxsection中的propagate選項,在logger物件把record傳遞給所有相關的handler的時候,會(逐級向上)尋找這個logger和它所有的父logger的全部handler。在尋找過程中,如果logger物件的propagate屬性被設定為1,那麼就繼續向上尋找;如果某個logger的propagate屬性被設定為0,那麼就會停止搜尋。

第二個是logger_xxxsection中的qualname選項,它其實就是logger的名稱。

使用配置檔案的時候,必須定義root logger。

最酷的listen(port)函式

logging.config.listen(port)函式可以讓應用程式在一個socket上監聽新的配置資訊,達到在執行時改變配置,而不用重啟應用程式的目的。

監聽程式:

import logging.config 
import logging 
import time
logging.config.fileConfig("logging.conf") 
logger = logging.getLogger("test_logging.listen")
t = logging.config.listen(9999) 
t.setDaemon(True) 
t.start()
try: 
 while True:
 logger.info('running.')
 time.sleep(3)
except (KeyboardInterrupt,SystemExit,Exception): 
 logging.config.stopListening()

傳送新的配置資訊程式:

import socket 
import struct
HOST = 'localhost' 
PORT = 9999 
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
s.connect((HOST,PORT)) 
print "connected..." 
data_to_send = open("logging.conf").read() 
s.send(struct.pack(">L",len(data_to_send))) 
s.send(data_to_send) 
print "closing..." 
s.close()

以上就是詳解python logging日誌傳輸的詳細內容,更多關於python logging日誌傳輸的資料請關注我們其它相關文章!