码农技术炒股之路——配置管理器、日志管理器
? ? ? ? 配置管理器和日志管理器是項(xiàng)目中最為獨(dú)立的模塊。我們可以很方便將其剝離出來(lái)供其他Python工程使用。文件的重點(diǎn)將是介紹Python單例和logging模塊的使用。(轉(zhuǎn)載請(qǐng)指明出于breaksoftware的csdn博客)
配置管理器
? ? ? ? 在《碼農(nóng)技術(shù)炒股之路——架構(gòu)和設(shè)計(jì)》中我們介紹過(guò),配置管理將作為一個(gè)單例而存在。我嘗試過(guò)各種Python單例的實(shí)現(xiàn)方法,發(fā)現(xiàn)都存在一些問(wèn)題,不能保證單例的特性。后來(lái)對(duì)一些方案進(jìn)行修改,得出下面一種可靠的方式:
instances = {}
def singleton(cls, *args, **kw):global instancesdef _singleton(*args, **kw): if cls.__name__ not in instances: instances[cls.__name__] = cls(*args, **kw)return instances[cls.__name__] return _singleton
? ? ? ? 我們可以使用下面方法進(jìn)行測(cè)試
@singletonclass singleton_test(object):def __init__(self, s_data):print "init"self._data = s_datadef run(self):print self._dataa = singleton_test("AAAAAAA")print aa.run()b = singleton_test("BBBBBBB")print bb.run()
? ? ? ? 其結(jié)果是
init
<__main__.singleton_test object at 0x021C57D0>
AAAAAAA
<__main__.singleton_test object at 0x021C57D0>
AAAAAAA
? ? ? ? 可見我們對(duì)通過(guò)修飾符修飾后的singleton_test類“構(gòu)造”兩次后得到的是同一個(gè)對(duì)象。
? ? ? ? 配置管理的實(shí)現(xiàn)其實(shí)非常簡(jiǎn)單,它就是一個(gè)使用singleton修飾、封裝了ConfigParser的單例類
import ConfigParser
from singleton import singleton@singleton
class scheduler_frame_conf_inst():def __init__(self):self._cp = Nonedef load(self, conf_path):print("load frame conf %s" % conf_path)self._cp = ConfigParser.SafeConfigParser()self._cp.read(conf_path)def has_option(self, section_name, option_name):if self._cp:return self._cp.has_option(section_name, option_name)return Falsedef get(self, section_name, option_name):if self._cp:return self._cp.get(section_name, option_name)else:print("get conf %s %s" % (section_name, option_name))
? ? ? ? load方法用于從指定路徑加載工程配置。因?yàn)樽幽K都有自己的配置,且可能格式不一致,所以如果這些配置都放在一個(gè)文件中會(huì)顯得非常雜亂。故工程的主配置文件保存是一組子模塊配置文件路徑的信息。子模塊通過(guò)自己的配置解釋規(guī)則去解釋這些文件。
[frame_job]
conf_path = ./conf/frame_job.conf[frame_log]
conf_path = ./conf/log.conf[strategy_job]
conf_path = ./conf/strategy_job.conf[mysql_manager]
conf_path = ./conf/mysql_manager.conf[regulars]
conf_path = ./conf/regulars_manager.conf
? ? ? ? 上面配置分別對(duì)應(yīng)于:系統(tǒng)任務(wù)管理器配置、日志管理器配置、普通任務(wù)管理器配置、數(shù)據(jù)庫(kù)管理配置和正則管理器配置。
日志管理器
? ? ? ? 日志管理是通過(guò)封裝Python的logging實(shí)現(xiàn)的。官方說(shuō)明并沒(méi)有對(duì)如何配置logging進(jìn)行詳細(xì)且準(zhǔn)確的說(shuō)明,所以我在完善這個(gè)模塊時(shí)進(jìn)行了若干次嘗試,才得出正確的使用方法。
? ? ? ? 一般來(lái)說(shuō)日志可以分為如下五種等級(jí):
- Info。用于記錄一般性日志,如執(zhí)行流程或者運(yùn)行中的中間結(jié)果。如果線上日志量比較大,這種日志在上線前是可以關(guān)閉的。
- Debug。用于記錄輔助調(diào)試的信息。如果線上日志量比較大,這種日志在上線前是可以關(guān)閉的。
- Warning。用于記錄運(yùn)行中我們可以接受的錯(cuò)誤。一般發(fā)生這種錯(cuò)誤只是一種預(yù)告,預(yù)示著某些方面出現(xiàn)了異常。
- Error。用于記錄運(yùn)行中我們可以勉強(qiáng)接受的錯(cuò)誤。這種錯(cuò)誤的發(fā)生并不代表我們整個(gè)工程不可用,而是某些功能已經(jīng)受限了。
- Fatal。用于記錄運(yùn)行中我們不可以接收的錯(cuò)誤。比如我們發(fā)現(xiàn)內(nèi)存分配失敗,就可以打印Fatal錯(cuò)誤,并退出程序。因?yàn)閮?nèi)存都耗盡了,之后發(fā)生的什么事都不好預(yù)測(cè),不如記錄下錯(cuò)誤信息后退出。
? ? ? ? Python的logging庫(kù)也支持上述等級(jí)。我們先看下封裝后的日志類初始化操作
@singleton
class loggingex():def __init__(self, conf_path):error = 0while True:try:logging.config.fileConfig(conf_path)except IOError as e:if error > 1:raise eif 2 == e.errno:if os.path.isdir(e.filename):os.makedirs(e.filename)else:os.makedirs(os.path.dirname(e.filename))error = error + 1except Exception as e: raise eelse:break
? ? ? ? 其最核心的就是logging.config.fileConfig(conf_path)這行。它讓logging庫(kù)通過(guò)一個(gè)配置文件進(jìn)行初始化,其中包括日志類型、日志格式和日志輸出方式等信息。這些配置如何編寫,以及如何結(jié)合代碼使用,將是后文介紹的重點(diǎn)。
? ? ? ? 為了讓封裝的日志管理器有更強(qiáng)大的功能。我提出以下設(shè)計(jì)要求:
- Debug等級(jí)日志只打印在Console中。
- Info等級(jí)日志只打印在普通日志文件中。按小時(shí)切分。
- Warning、Error和Fatal等級(jí)日志只打印在錯(cuò)誤日志文件中。按小時(shí)切分。
? ? ? ? 我們先看下針對(duì)Debug等級(jí)日志的配置方式。
打印在Console中的Debug等級(jí)日志
? ? ? ? 首先我們需要定義日志輸出的格式。我們希望日志可以打印出:時(shí)間、等級(jí)、進(jìn)程ID、線程ID和用戶自定義消息。這樣在配置文件中我們需要加入如下的內(nèi)容
[formatter_LogFormatter]
format=%(asctime)s ^ %(levelname)s ^ %(process)d ^ %(thread)d ^ %(message)s
datefmt=
class=logging.Formatter
? ? ? ? 注意一下,這個(gè)節(jié)的名稱是formatter_LogFormatter。但是實(shí)際我們之后要使用的名稱只有下劃線之后一節(jié)——LogFormatter。“formatter_”是格式配置名的固有信息,即任何格式配置都要使用它來(lái)開頭。
? ? ? ? 然后我們要聲明一個(gè)叫formatters節(jié),其下keys包含了之前聲明的格式配置名稱
[formatters]
keys=LogFormatter
? ? ? ? 下一步我們要聲明日志輸出方式。因?yàn)镈ebug日志是輸出到Console中的,所以我們使用的類是StreamHandler
[handler_ConsoleHandler]
class=StreamHandler
formatter=LogFormatter
level=DEBUG
args=(sys.stdout,)
? ? ? ? 上面一節(jié)記錄了日志輸出所使用的類名、所使用的格式、日志等級(jí)和輸出參數(shù)。注意一下節(jié)的名稱——handler_ConsoleHandler,和格式配置節(jié)名要以“formatter_”開始類似,輸出方式的節(jié)名要以“handler_”開頭,而實(shí)際的名稱則是下劃線之后的ConsoleHandler。
? ? ? ? 接下來(lái)我們需要聲明一個(gè)叫handlers的節(jié),其下keys包含了之前聲明的輸出方式配置名稱
[handlers]
keys=ConsoleHandler
? ? ? ? 最后我們要聲明一個(gè)叫l(wèi)oggers的節(jié),其下keys字段它包含了日志對(duì)象的名稱。這些名稱用逗號(hào)分隔。在定義Debug等級(jí)日志對(duì)象名稱前,我們先要定義一個(gè)叫root的日志對(duì)象
[loggers]
keys=root,LogDebug
? ? ? ? root日志對(duì)象的配置要包含所有聲明的初始方式信息,當(dāng)前我們只有ConsoleHandler,于是這樣配置
[logger_root]
level=NOTSET
handlers=ConsoleHandler
? ? ? ??LogDebug的配置如下
[logger_LogDebug]
handlers=ConsoleHandler
qualname=logger_LogDebug
level=DEBUG
propagate=0
? ? ? ? 節(jié)名是以logger_開頭,其后跟著在Loggers中聲明的Debug日志對(duì)象名稱LogDebug。handler指向向Console輸出的輸出方式名ConsoleHandler;qualname指定為該節(jié)節(jié)名。level設(shè)置為DEBUG。
? ? ? ? 在Python中,我們可以通過(guò)下面的方式使用該日志對(duì)象
def log_debug(self, msg):log_debug = logging.getLogger('logger_LogDebug') #https://docs.python.org/2/howto/logging.htmllog_debug.debug(msg)
打印在文件中、按時(shí)間切分的Info等級(jí)日志
? ? ? ? 數(shù)據(jù)的內(nèi)容格式我們還是借用LogFormatter定義。因?yàn)檫@次是要往文件中輸出,所以我們需要重新定義一種輸出方式——FileNomalHandler。
[handler_FileNomalHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=LogFormatter
level=INFO
args=('./log/nomal.log.i', 'H', 1, 60)
? ? ? ? 因?yàn)橐磿r(shí)間維度切分,所以這次使用的類是logging.handlers.TimedRotatingFileHandler。然后我們?cè)賏rgs中指定文件生成的路徑和通用名,以及按多久時(shí)間切分一次。上述寫法,將導(dǎo)致logging在工程的log目錄下生成nomal.log.i.2017-01-01_23這樣格式的數(shù)據(jù)
? ? ? ? 別忘記修改handlers下的keys信息,要把新增的handler給加進(jìn)去
[handlers]
keys=ConsoleHandler,FileNomalHandler,
? ? ? ? 以及在logger_root加入它
[logger_root]
level=NOTSET
handlers=ConsoleHandler,FileNomalHandler
? ? ? ? 相應(yīng)的我們需要定義一個(gè)日志對(duì)象配置
[logger_LogInfo]
handlers=FileNomalHandler
qualname=logger_LogInfo
level=INFO
propagate=0
? ? ? ? 并在loggers下的keys中新增該對(duì)象名稱
[loggers]
keys=root,LogDebug,LogInfo
打印在文件中、按時(shí)間切分的Warning、Error和Fatal等級(jí)日志
? ? ? ? 相應(yīng)的配置修改和上面類似,當(dāng)時(shí)要注意文件名稱需要換一下。我把整個(gè)配置放在這面區(qū)域中
###############################################################################
[loggers]
keys=root,LogDebug,LogInfo,LogWarningErrorCritical,SQL_ERROR[logger_root]
level=NOTSET
handlers=ConsoleHandler,FileNomalHandler,FileErrorHandler[logger_LogDebug]
handlers=ConsoleHandler
qualname=logger_LogDebug
level=DEBUG
propagate=0[logger_LogInfo]
handlers=FileNomalHandler
qualname=logger_LogInfo
level=INFO
propagate=0[logger_LogWarningErrorCritical]
handlers=FileErrorHandler
qualname=logger_LogWarningErrorCritical
level=WARNING
propagate=0[logger_SQL_ERROR]
handlers=SaveErrorSQL_FileHandler
qualname=logger_SQL_ERROR
level=WARNING
propagate=0
##############################################################################################################################################################
[handlers]
keys=ConsoleHandler,FileNomalHandler,FileErrorHandler,SaveErrorSQL_FileHandler[handler_ConsoleHandler]
class=StreamHandler
formatter=LogFormatter
level=DEBUG
args=(sys.stdout,)[handler_FileNomalHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=LogFormatter
level=INFO
args=('./log/nomal.log.i', 'H', 1, 60)[handler_FileErrorHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=LogFormatter
level=WARNING
args=('./log/nomal.log.wec', 'H', 1, 60)[handler_SaveErrorSQL_FileHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=SQLLogFormatter
level=WARNING
args=('./log/sql_error.log', 'H', 1, 60)##############################################################################################################################################################
[formatters]
keys=LogFormatter,SQLLogFormatter[formatter_LogFormatter]
format=%(asctime)s ^ %(levelname)s ^ %(process)d ^ %(thread)d ^ %(message)s
datefmt=
class=logging.Formatter[formatter_SQLLogFormatter]
format=%(asctime)s ^ %(message)s
datefmt=
class=logging.Formatter
###############################################################################
? ? ? ? 上面配置中,我新增了一個(gè)打印SQL的日志對(duì)象配置。因?yàn)槲覀冎缶S護(hù)時(shí)可能需要把執(zhí)行失敗的SQL重新執(zhí)行一遍,所以需要一個(gè)盡量簡(jiǎn)潔的文件格式。
? ? ? ? 因?yàn)樵谌罩局?#xff0c;我需要知道是哪個(gè)文件哪行出錯(cuò),所以需要使用inspect庫(kù)進(jìn)行棧回溯。于是在用戶自定義消息的基礎(chǔ)上,在調(diào)用日志方法前,對(duì)原消息做些修改(除了SQL日志)以擴(kuò)充信息。
? ? ? ? 完整的代碼如下:
import os
import sys
import inspect
import logging
import logging.config
from singleton import singleton@singleton
class loggingex():def __init__(self, conf_path):error = 0while True:try:logging.config.fileConfig(conf_path)except IOError as e:if error > 1:raise eif 2 == e.errno:if os.path.isdir(e.filename):os.makedirs(e.filename)else:os.makedirs(os.path.dirname(e.filename))error = error + 1except Exception as e: raise eelse:breakdef log_debug(self, msg):log_debug = logging.getLogger('logger_LogDebug') #https://docs.python.org/2/howto/logging.htmllog_debug.debug(msg)def log_info(self, msg):log_info = logging.getLogger('logger_LogInfo')log_info.info(msg)def log_warning(self, msg):log_warning_error_critical = logging.getLogger('logger_LogWarningErrorCritical')log_warning_error_critical.warning(msg)def log_error(self, msg):log_warning_error_critical = logging.getLogger('logger_LogWarningErrorCritical')log_warning_error_critical.error(msg) def log_critical(self, msg):log_warning_error_critical = logging.getLogger('logger_LogWarningErrorCritical')log_warning_error_critical.critical(msg)def log_error_sql(self, msg):log_error_sql = logging.getLogger('logger_SQL_ERROR')log_error_sql.critical(msg)def LOG_INIT(conf_path):global logger_objlogger_obj = loggingex(conf_path)def modify_msg(msg):stack_info = inspect.stack()if len(stack_info) > 2:file_name = inspect.stack()[2][1]line = inspect.stack()[2][2]function_name = inspect.stack()[2][3]new_msg = file_name + " ^ " + function_name + " ^ " + str(line) + " ^ " + msgreturn new_msgdef LOG_DEBUG(msg):new_msg = modify_msg(msg)try:logger_obj.log_debug(new_msg)except Exception as e:print new_msgdef LOG_INFO(msg):new_msg = modify_msg(msg)try:logger_obj.log_info(new_msg)except Exception as e:print new_msgdef LOG_WARNING(msg):new_msg = modify_msg(msg)try:logger_obj.log_warning(new_msg)except Exception as e:print new_msgdef LOG_ERROR(msg):new_msg = modify_msg(msg)try:logger_obj.log_error(new_msg)except Exception as e:print new_msgdef LOG_CRITICAL(msg):new_msg = modify_msg(msg)try:logger_obj.log_critical(new_msg)except Exception as e:print new_msgdef LOG_ERROR_SQL(msg):try:logger_obj.log_error_sql(msg)except Exception as e:print msgif __name__ == "__main__":LOG_INIT("../../conf/log.conf")LOG_DEBUG('LOG_DEBUG')LOG_INFO('LOG_INFO')LOG_WARNING('LOG_WARNING')LOG_ERROR('LOG_ERROR')LOG_CRITICAL('LOG_CRITICAL')LOG_ERROR_SQL("Create XXX Error")
總結(jié)
以上是生活随笔為你收集整理的码农技术炒股之路——配置管理器、日志管理器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C++拾趣——类构造函数的隐式转换
- 下一篇: 码农技术炒股之路——数据库管理器、正则表