码农技术炒股之路——配置管理器、日志管理器
配置管理器和日志管理器是项目中最为独立的模块。我们可以很方便将其剥离出来供其他Python工程使用。文件的重点将是介绍Python单例和logging模块的使用。(转载请指明出于breaksoftware的csdn博客)
配置管理器
在《码农技术炒股之路——架构和设计》中我们介绍过,配置管理将作为一个单例而存在。我尝试过各种Python单例的实现方法,发现都存在一些问题,不能保证单例的特性。后来对一些方案进行修改,得出下面一种可靠的方式:
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
我们可以使用下面方法进行测试
@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()
其结果是
init
<__main__.singleton_test object at 0x021C57D0>
AAAAAAA
<__main__.singleton_test object at 0x021C57D0>
AAAAAAA
可见我们对通过修饰符修饰后的singleton_test类“构造”两次后得到的是同一个对象。
配置管理的实现其实非常简单,它就是一个使用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方法用于从指定路径加载工程配置。因为子模块都有自己的配置,且可能格式不一致,所以如果这些配置都放在一个文件中会显得非常杂乱。故工程的主配置文件保存是一组子模块配置文件路径的信息。子模块通过自己的配置解释规则去解释这些文件。
[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
上面配置分别对应于:系统任务管理器配置、日志管理器配置、普通任务管理器配置、数据库管理配置和正则管理器配置。
日志管理器
日志管理是通过封装Python的logging实现的。官方说明并没有对如何配置logging进行详细且准确的说明,所以我在完善这个模块时进行了若干次尝试,才得出正确的使用方法。
一般来说日志可以分为如下五种等级:
- Info。用于记录一般性日志,如执行流程或者运行中的中间结果。如果线上日志量比较大,这种日志在上线前是可以关闭的。
- Debug。用于记录辅助调试的信息。如果线上日志量比较大,这种日志在上线前是可以关闭的。
- Warning。用于记录运行中我们可以接受的错误。一般发生这种错误只是一种预告,预示着某些方面出现了异常。
- Error。用于记录运行中我们可以勉强接受的错误。这种错误的发生并不代表我们整个工程不可用,而是某些功能已经受限了。
- Fatal。用于记录运行中我们不可以接收的错误。比如我们发现内存分配失败,就可以打印Fatal错误,并退出程序。因为内存都耗尽了,之后发生的什么事都不好预测,不如记录下错误信息后退出。
Python的logging库也支持上述等级。我们先看下封装后的日志类初始化操作
@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库通过一个配置文件进行初始化,其中包括日志类型、日志格式和日志输出方式等信息。这些配置如何编写,以及如何结合代码使用,将是后文介绍的重点。
为了让封装的日志管理器有更强大的功能。我提出以下设计要求:
- Debug等级日志只打印在Console中。
- Info等级日志只打印在普通日志文件中。按小时切分。
- Warning、Error和Fatal等级日志只打印在错误日志文件中。按小时切分。
我们先看下针对Debug等级日志的配置方式。
打印在Console中的Debug等级日志
首先我们需要定义日志输出的格式。我们希望日志可以打印出:时间、等级、进程ID、线程ID和用户自定义消息。这样在配置文件中我们需要加入如下的内容
[formatter_LogFormatter]
format=%(asctime)s ^ %(levelname)s ^ %(process)d ^ %(thread)d ^ %(message)s
datefmt=
class=logging.Formatter
注意一下,这个节的名称是formatter_LogFormatter。但是实际我们之后要使用的名称只有下划线之后一节——LogFormatter。“formatter_”是格式配置名的固有信息,即任何格式配置都要使用它来开头。
然后我们要声明一个叫formatters节,其下keys包含了之前声明的格式配置名称
[formatters]
keys=LogFormatter
下一步我们要声明日志输出方式。因为Debug日志是输出到Console中的,所以我们使用的类是StreamHandler
[handler_ConsoleHandler]
class=StreamHandler
formatter=LogFormatter
level=DEBUG
args=(sys.stdout,)
上面一节记录了日志输出所使用的类名、所使用的格式、日志等级和输出参数。注意一下节的名称——handler_ConsoleHandler,和格式配置节名要以“formatter_”开始类似,输出方式的节名要以“handler_”开头,而实际的名称则是下划线之后的ConsoleHandler。
接下来我们需要声明一个叫handlers的节,其下keys包含了之前声明的输出方式配置名称
[handlers]
keys=ConsoleHandler
最后我们要声明一个叫loggers的节,其下keys字段它包含了日志对象的名称。这些名称用逗号分隔。在定义Debug等级日志对象名称前,我们先要定义一个叫root的日志对象
[loggers]
keys=root,LogDebug
root日志对象的配置要包含所有声明的初始方式信息,当前我们只有ConsoleHandler,于是这样配置
[logger_root]
level=NOTSET
handlers=ConsoleHandler
LogDebug的配置如下
[logger_LogDebug]
handlers=ConsoleHandler
qualname=logger_LogDebug
level=DEBUG
propagate=0
节名是以logger_开头,其后跟着在Loggers中声明的Debug日志对象名称LogDebug。handler指向向Console输出的输出方式名ConsoleHandler;qualname指定为该节节名。level设置为DEBUG。
在Python中,我们可以通过下面的方式使用该日志对象
def log_debug(self, msg):log_debug = logging.getLogger('logger_LogDebug') #https://docs.python.org/2/howto/logging.htmllog_debug.debug(msg)
打印在文件中、按时间切分的Info等级日志
数据的内容格式我们还是借用LogFormatter定义。因为这次是要往文件中输出,所以我们需要重新定义一种输出方式——FileNomalHandler。
[handler_FileNomalHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=LogFormatter
level=INFO
args=('./log/nomal.log.i', 'H', 1, 60)
因为要按时间维度切分,所以这次使用的类是logging.handlers.TimedRotatingFileHandler。然后我们再args中指定文件生成的路径和通用名,以及按多久时间切分一次。上述写法,将导致logging在工程的log目录下生成nomal.log.i.2017-01-01_23这样格式的数据
别忘记修改handlers下的keys信息,要把新增的handler给加进去
[handlers]
keys=ConsoleHandler,FileNomalHandler,
以及在logger_root加入它
[logger_root]
level=NOTSET
handlers=ConsoleHandler,FileNomalHandler
相应的我们需要定义一个日志对象配置
[logger_LogInfo]
handlers=FileNomalHandler
qualname=logger_LogInfo
level=INFO
propagate=0
并在loggers下的keys中新增该对象名称
[loggers]
keys=root,LogDebug,LogInfo
打印在文件中、按时间切分的Warning、Error和Fatal等级日志
相应的配置修改和上面类似,当时要注意文件名称需要换一下。我把整个配置放在这面区域中
###############################################################################
[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
###############################################################################
上面配置中,我新增了一个打印SQL的日志对象配置。因为我们之后维护时可能需要把执行失败的SQL重新执行一遍,所以需要一个尽量简洁的文件格式。
因为在日志中,我需要知道是哪个文件哪行出错,所以需要使用inspect库进行栈回溯。于是在用户自定义消息的基础上,在调用日志方法前,对原消息做些修改(除了SQL日志)以扩充信息。
完整的代码如下:
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")
码农技术炒股之路——配置管理器、日志管理器相关推荐
- 码农技术炒股之路——任务管理器
系统任务和普通任务都是通过任务管理器调度的.它们的区别是:系统任务在程序运行后即不会被修改,而普通任务则会被修改.(转载请指明出于breaksoftware的csdn博客) 为什么要有这样的设计?因为 ...
- 码农技术炒股之路——数据库管理器、正则表达式管理器
我选用的数据库是Mysql.选用它是因为其可以满足我的需求,而且资料多.因为作为第三方工具,难免有一些配置问题.所以本文也会讲解一些和Mysql配置及开发相关的问题.(转载请指明出于breaksoft ...
- 码农技术炒股之路——抓取股票基本信息、实时交易信息、主力动向信息
从本节开始,我们开始介绍各个抓取和备份业务.(转载请指明出于breaksoftware的csdn博客) 因为我们数据库很多,数据库中表也很多,所以我们需要一个自动检测并创建数据库和表的功能.在< ...
- 码农技术炒股之路——抓取日线数据、计算均线和除权数据
日线数据是股票每日收盘后的信息.这块数据不用实时抓取,所以并不占用宝贵的交易时间的资源.于是我们抓取完数据后直接往切片后的数据库中保存.(转载请指明出于breaksoftware的csdn博客) 抓取 ...
- 码农技术炒股之路——实时交易信息、主力动向信息分库备份
一般来说,一个股票信息应该保存在一张表中.但是由于我机器资源限制,且我希望尽快频率的抓取数据.所以每天我将所有股票的实时交易信息放在daily_temp库中的一个以日期命名的表中.主力动向信息也是如此 ...
- KBMMW 的日志管理器
kbmmw 4.82 最大的新特性就是增加了 日志管理器. 新的日志管理器实现了不同类型的日志.断言.异常处理.计时等功能. 首先.引用kbmMWLog.pas 单元后,系统就默认生成一个IkbmMW ...
- Canal binlog 日志管理器与GTID简介
本节目录 1.Canal 位点管理(日志管理器) 1.1 类图 1.2 日志管理器使用方法 2.MySQL GTID 扫盲 正如上文提到的那样,在 Canal Instance 启动的时候,首先会查询 ...
- java日志——修改日志管理器配置+日志本地化
[0]README 0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java日志--修改日志管理器配置+日志本地化 的相关知识: [1]修改日志管理器配置 1.1 ...
- PostgreSQL multixact日志管理器说明
PostgreSQL multixact日志管理器说明 MultiXactID日志是uxdb系统用来记录组合事务ID的一种日志.由于uxdb采用了多版本并发控制,因此同一个元组相关联的事务ID可能有多 ...
最新文章
- C# WebAPI中DateTime类型字段在使用微软自带的方法转json格式后默认含T的解决办法...
- Unable to generate a temporary class (result=1)解决方法
- 【转载】浅谈React编程思想
- 互联网日报 | 3月12日 星期五 | 京东全年净增1.1亿活跃用户;百度计划3月23日登陆港交所;中国联通首次公布5G用户数...
- 利用 assistant_如何使用Dialogflow对Google Assistant操作实施本地履行
- 穿越鸿蒙电视剧,电视剧大泼猴剧情介绍(1-45全集)
- 想成为大数据分析师必须知道的这些事儿(文末福利)
- python字符串及基本运算
- ORA-01501: CREATE DATABASE failed
- java私塾初级_java私塾 Java初级视频教程
- python画spc控制图_如何选择最适合我们的SPC控制图?
- VMware系列序列号
- 关于2020计算机考研。
- python 谷歌翻译接口_使用python调用谷歌翻译接口实现英文到中文的翻译
- 三七互娱秋招web前端笔试题编程题(使用原生JS实现一个英雄类Hero, 可以按照以下方式调用正确输出)
- 经济学人The Economist学习(笔记词汇)Day1
- 机器人运动控制-水下机器人
- 【arcpy批量制图】符号系统的色带、小数点设置
- 坐标旋转公式-- 角度和弧度
- CSS雪碧图demo(含雪碧代码)
热门文章
- keras 的 example 文件 mnist_denoising_autoencoder.py 解析
- OpenCV(基础补充)颜色空间HSV *args与**args(滑动条传参问题)
- hibernate连接mysql数据库步骤_Hibernate (操作步骤)
- mysql替换开头_如何在MySQL的字符串开头搜索和替换特定字符?
- python 去除str的引号 去除括号_Python基础教程:运算符以及数据类型解析
- boost::asio中的C/S同步实例源码
- 3dsMax插件V-Ray渲染与合成学习课程 3ds Max: Rendering for Compositing in V-Ray Next
- 通过IP地址和子网掩码与运算计算相关地址
- 磁盘I:O 性能指标 以及 如何通过 fio 对nvme ssd,optane ssd, pmem 性能摸底
- linux进程间通信:FIFO应用 /var/log/ 系统日志的模拟实现