官方文檔:Logging facility for Python — Python 3 documentation
基本用法
logging默認配置了六種日志級別(括號為級別對應的數值),按優先級升序,依次為:
- NOTSET(0)
- DEBUG(10)
- INFO(20)
- WARNING(30)
- ERROR(40)
- CRITICAL(50)
logging 執行時輸出大於等於設置的日志級別的日志信息,如設置日志級別是 INFO,則 INFO、WARNING、ERROR、CRITICAL 級別的日志都會輸出
先來看一個簡單的用法示例:
import logging
logging.basicConfig() # 自動化配置
logging.warning('This is a warning message') # 默認的日志輸出級別為Warning
logging.basicConfig(**kwargs)
中常用的參數包括:
filename
:指定使用指定的文件名而不是 StreamHandler 創建 FileHandlerfilemode
:如果指定 filename,則以此模式打開文件(‘r’、‘w’、‘a’)。默認為“a”format
:配置日志格式字符串,參考:logrecord-attributesdatefmt
:配置時間格式字符串,支持time.strftime() 所接受的日期/時間格式level
:指定日志記錄器級別,默認為WARNINGhandlers
:指定handlers,注意此參數與filename和filemode不兼容,同時配置時會報錯
basicConfig配置示例:
logging.basicConfig(filename="test.log",
filemode="w",
format="%(asctime)s %(name)s:%(levelname)s:%(message)s",
datefmt="%d-%M-%Y %H:%M:%S",
level=logging.DEBUG)
進階配置
我們先了解一下logging中的主要模塊:
- Loggers:expose the interface that application code directly uses.
- Handlers:send the log records (created by loggers) to the appropriate destination.
- Filters:provide a finer grained facility for determining which log records to output.
- Formatters:specify the layout of log records in the final output.
程序中發送的日志信息將被包裝成LogRecord對象傳入logging的各個組件中:
Loggers
Logger
objects have a threefold job. First, they expose several methods to application code so that applications can log messages at runtime. Second, logger objects determine which log messages to act upon based upon severity (the default filtering facility) or filter objects. Third, logger objects pass along relevant log messages to all interested log handlers.
Logger與handler之間的關系類似於郵件和郵箱,一封郵件可以在需要的時候抄送至多個收件箱。Logger常用的配置方法包括:
Logger.setLevel()
:設置最低顯示級別Logger.addHandler()
和Logger.removeHandler()
:增刪Handlers,每個Logger可以對應0個或多個handlerLogger.addFilter()
和Logger.removeFilter()
:增刪格式配置項
每個Logger都有一個name屬性,它代表了這個logger在用戶程序中所屬的模塊(與作用域的概念類似),不同模塊下logger的name可以通過.
來組織層級關系,比如hook
,hook.spark
,hook.spark.attachment
等。不同層級的logger間有類似於編程對象中“繼承”的關系:父logger的各種配置項都會被子logger繼承(包括handler,filter等)
我們可以通過logging.getLogger()
方法獲得logger對象並配置name。由於logger遵從單例模式,因此多次調用getLogger()並配置相同的name時,該接口將返回同一個logger對象
Handlers
何時我們需要多個Handler?
As an example scenario, an application may want to send all log messages to a log file, all log messages of error or higher to stdout, and all messages of critical to an email address.
標准庫中提供了一系列預定義的Handler,參考: Useful Handlers,它們常用的配置方法包括:
setLevel()
設置handler將處理哪個級別以上的消息setFormatter()
為Handler指定一個格式配置對象addFilter()
andremoveFilter()
增刪Filter
Formatters
formatter用來配置日志的各種格式,它包括三個參數:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
其中:
fmt
指定了日志的消息格式,如:'%(asctime)s - %(levelname)s - %(message)s'
datefmt
指定了日期的組織格式,如:%Y-%m-%d %H:%M:%S
style
允許用戶指定用什么類型的標識符來描述前兩個參數的內容。比如,在以上兩行例子中,我們用的都是%
標識符。除此之外,我們還可以用{}
和$
標識符,具體內容請參考:LogRecord attributes
整體配置
Logging可以通過三種方法配置:
- 在python代碼中顯式地聲明loggers、handlers和formatters等組件並調用相關方法進行配置
- 創建一個logging配置文件,並調用
fileConfig()
進行配置 - 聲明一個包含配置信息的dict,並將它傳入
dictConfig()
進行配置
后兩種配置方式的具體細節可參閱: Configuration functions,接下來展示一個第一種配置方式的簡單demo:
import logging
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# add formatter to ch
ch.setFormatter(formatter)
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# add ch to logger
logger.addHandler(ch)
# 'application' code
logger.debug('debug message')
以上代碼先創建了一個formatter,然后把它添加至handler,接着把該handler關聯至一個logger,之后我們就可以用這個logger記錄信息了
代碼樣例
一個日志模塊封裝樣例:
import os
import logging
from logging.handlers import RotatingFileHandler
class Log:
_logger = None
_log_dir = None
@staticmethod
def get():
if Log._logger:
return Log._logger
else:
return Log._build_logger()
@staticmethod
def set_dir(file_dir):
if Log._log_dir:
raise Exception('log directory has already been set. (Check if "get()" has been called before)')
else:
if not (os.path.exists(file_dir) and os.path.isdir(file_dir)):
os.mkdir(file_dir)
Log._log_dir = file_dir
@staticmethod
def filter(name, level):
level = str.upper(level)
assert level in {'DEBUG', 'INFO', 'WARNING', 'ERROR'}
logging.getLogger(name).setLevel(getattr(logging, level))
@staticmethod
def _build_logger():
log_fmt = '[%(asctime)s | \"%(filename)s\" line %(lineno)s | %(levelname)s] %(message)s'
formatter = logging.Formatter(log_fmt, datefmt="%Y/%m/%d %H:%M:%S")
if not Log._log_dir:
os.makedirs('logs', exist_ok=True)
Log._log_dir = 'logs'
log_filepath = os.path.join(Log._log_dir, "rotating.log")
log_file_handler = RotatingFileHandler(filename=log_filepath, maxBytes=500, backupCount=3)
log_file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logging.basicConfig(level=logging.INFO, handlers=(log_file_handler, stream_handler))
Log._logger = logging.getLogger()
return Log._logger
if __name__ == '__main__':
import time
# Log.set_dir('logs') # Configure the log directory before use, if needed.
log = Log.get()
Log.set_dir('logs')
for count in range(20):
log.error(f"logger count: {count}")
time.sleep(1)
另一種寫法,根據調用者的文件位置自動給logger命名:
import os
import logging
import traceback
from logging.handlers import RotatingFileHandler
class Log:
_root = None
_logger = None
@staticmethod
def get():
# Construct the name of the logger based on the file path
code_file = traceback.extract_stack()[-2].filename # Get the file path of the caller
if Log._root not in os.path.abspath(code_file):
raise Exception(f'The file calling the method is outside the home directory: "{code_file}"')
relpath = os.path.relpath(code_file, Log._root).replace('.py', '').replace('/', '.')
root_name = os.path.basename(Log._root)
return logging.getLogger(f"{root_name}.{relpath}")
@staticmethod
def init(home):
assert os.path.isdir(home), f'invalid home directory: "{home}"'
Log._root = os.path.abspath(home)
log_dir = os.path.join(Log._root, 'logs')
if not os.path.isdir(log_dir):
os.mkdir(log_dir)
Log._configure_root_logger(log_dir)
@staticmethod
def filter(name, level):
level = str.upper(level)
assert level in {'DEBUG', 'INFO', 'WARNING', 'ERROR'}
logging.getLogger(name).setLevel(getattr(logging, level))
@staticmethod
def _configure_root_logger(log_dir):
log_fmt = '[%(asctime)s | %(name)s | %(levelname)s] %(message)s'
formatter = logging.Formatter(log_fmt, datefmt="%Y/%m/%d %H:%M:%S")
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
log_filepath = os.path.join(log_dir, "rotating.log")
log_file_handler = RotatingFileHandler(filename=log_filepath, maxBytes=1e5, backupCount=3)
log_file_handler.setFormatter(formatter)
root_logger_name = os.path.basename(Log._root)
root_logger = logging.getLogger(root_logger_name)
root_logger.addHandler(log_file_handler)
root_logger.addHandler(stream_handler)
root_logger.setLevel(logging.DEBUG) # default level
if __name__ == '__main__':
import os, time
log_dir = os.path.dirname(__file__)
Log.init(log_dir) # project root directory
log = Log.get()
for count in range(20):
log.info(f"logger count: {count}")
time.sleep(1)