# logging_hivenet模块说明 logging_hivenet模块重新封装了python的logging模块,提供一个更便于使用的日志处理类。 ## logging_hivenet的简单使用 **1、装载要使用的对象** ``` import HiveNetCore.logging_hivenet as simple_log ``` **2、程序本地创建“logger.conf”文件,修改配置文件为自己希望的内容** 注意:conf文件中请不要含中文内容,原因是目前的程序对中文处理存在转码问题(问题待解决) 主要修改的参数如下: ​ (1)[handler_FileHandler]下的args参数: ​ 第1个参数是日志文件名(可以带路径),日志程序会自动在扩展名前补充日期; ​ 第2个参数固定为追加模式; ​ 第3个参数为自动转存的文件大小,单位为byte; ​ 第4个参数为自动转存时,超过多少个文件后以覆盖方式记录(不会再新增) ​ (2)[formatters]下的format参数,该参数定义了日志的格式 补充说明:也可以是json或xml文件,内容都一样,只是格式不同,配置说明在后面介绍 **3、实例化Logger对象并使用** ``` # 实例化对象,根据需要传入不同参数 _logger = simple_log.Logger(...) # 写日志 _logger.log(simple_log.INFO, '要写入的日志') # 其他写入方式 _logger.info('要写入的INFO日志') _logger.debug('要写入的Debug日志') ``` **4、使用注意问题** - logger类是全局共享的,即如果loggername、handler的配置名一样,建立多个logger类且进行调整的情况下会互相干扰,因此如果需要实例化多个logger,则建议配置名和handler名有所区分 - Python默认的FileHandler是线程安全的(支持多线程),但不是进程安全(不支持启动多进程记录同一日志),如果需要支持多进程,需要使用第三方的FileHandler,使用方式如下: ​ (1)pip install ConcurrentLogHandler ​ (2)修改日志配置文件的class=handlers.RotatingFileHandler为class=handlers.ConcurrentRotatingFileHandler ## 日志调用特殊参数说明 **Logger初始化参数** ``` @param {EnumLoggerName|string} logger_name=EnumLoggerName.Console - 输出日志类型,默认的3个类型如下:Console-输出到屏幕,File-输出到文件,ConsoleAndFile-同时输出到屏幕和文件;如果自己自定义了日志模块名,可以直接使用字符串方式传值使用(例如'myLoggerName') @param {string} json_str=None - 当日志配置方式为JSON_STR时使用,配置的字符串,如果不串则默认使用_LOGGER_DEFAULT_JSON_CONSOLE_STR的值 @param {bool} auto_create_conf=True - 如果配置方式是文件的情况,指定是否利用默认的参数自动创建配置文件(找不到指定的配置文件时),默认为True @param {bool} is_create_logfile_by_day=True - 指定是否按天生成新的日志文件,默认为True @param {int} call_fun_level=0 - 指定log函数输出文件名和函数名的层级,当自己对日志函数再封装了几层的情况下,无法打印到实际所需要登记的函数时,可以指定从向上几级来获取真实调用函数;0代表获取直接调用函数;1代表获取直接调用函数的上一级 @param {string} logfile_path='' - 日志输出文件的路径(含文件名),如果已有配置文件的情况下该参数无效,不传值时代表使用'log/程序名.log'来定义输出文件的路径 ``` **log写日志** ``` @param {int} - 输出日志的级别,可以使用simple_log.INFO这类值,也可以直接使用标准logging.INFO这样的传值 @param {**kwargs} args - 通用日子类的kwargs参数,以下为特殊参数的说明 extra {dict} - 用于传递日志上下文的字典,可用于额外添加一些上下文内容 例如可传入{'ip': '113.208.78.29', 'username': 'Petter'},这样当Formatter为 '%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s'时可以正常输出日志值 以下是simple_log定义的一些特殊上下文: callFunLevel {int} - 日志中输出的函数名(文件名)所属层级: 0 - 输出调用本函数的函数名(文件名) 1 - 输出调用本函数的函数的上1级函数的函数名(文件名) n - 输出调用本函数的函数的上n级函数的函数名(文件名) dealMsgFun {function} - 自定义日志内容修改函数,可以在输出日志前动态修改日志内容(msg) 函数格式为funs(topic_name, record){return msg_string},返回生成后的日志msg内容 topicName {string} - 日志主题,与dealMsgFun配套使用 ``` ## 日志配置文件详解 以下以INI文件的格式说明配置项的内容,其他格式的是同样道理: ``` ############################################### # 以下部分定义logger模块,root是父类,必需存在的,其它的是自定义的模块名(例子中的模块名是默认创建的,使用者可以自行定义其他模块名) # logging.getLogger(模块名称) 相当于向根据指定模块名的定义实例化一个日志操作对象 # 每个[logger_模块名称] 实际定义了一个模块名称和对应的处理句柄类 # level 日志输出级别,有DEBUG、INFO、WARNING、ERROR、CRITICAL,可设置不同级别进行日志的过滤 # handlers 处理句柄类,一个日志模块可以引用多个处理句柄,用逗号分开,用来实现同一日志向多个地方输出 # qualname logger名称,应用程序通过 logging.getLogger获取。对于不能获取的名称,则记录到root模块 # propagate 是否继承父类的log信息,0:否 1:是 ############################################### [loggers] keys=root,Console,File,ConsoleAndFile [logger_root] level=DEBUG handlers= [logger_Console] level=DEBUG handlers=ConsoleHandler [logger_File] handlers=FileHandler qualname=File propagate=0 [logger_ConsoleAndFile] handlers=ConsoleHandler,FileHandler qualname=ConsoleAndFile propagate=0 ############################################### # 以下部分定义处理句柄及相关参数: # [handlers] 指定配置里定义的句柄名清单 # [handler_句柄名] 定义了具体句柄的具体传入参数,简要说明如下: # class - 句柄的类对象路径(按照python标准访问类的形式),要求在代码中必须能通过该路径访问类 # level - 句柄对应的日志级别,可设置不同级别进行日志的过滤 # formatter - 指定句柄对应的日志格式定义,为[formatters]章节的格式名 # args - 句柄类初始化的传入参数,按照不同的句柄有不同的定义 # # 可以使用python自带的句柄类、第三方库中的句柄类,也可以自行开发自己的句柄类,部分官方句柄类说明如下: # StreamHandler : 使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息。它的构造函数是: # StreamHandler([strm]) # 其中strm参数是一个文件对象。默认是sys.stderr # FileHandler : 和StreamHandler类似,用于向一个文件输出日志信息。不过FileHandler会帮你打开这个文件。它的构造函数是: # FileHandler(filename[,mode]) # filename是文件名,必须指定一个文件名。 # mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a',即添加到文件末尾。 # handlers.RotatingFileHandler : 这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出。比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2...;最后重新创建 chat.log,继续输出日志信息。它的构造函数是: # RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]]) # 其中filename和mode两个参数和FileHandler一样。 # maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。 # backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。 ############################################### [handlers] keys=ConsoleHandler,FileHandler [handler_ConsoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,) [handler_FileHandler] class=handlers.RotatingFileHandler level=INFO formatter=simpleFormatter args=('myapp.log', 'a', 10*1024*1024, 1000) ############################################### # 以下部分定义输出的格式和内容: # [formatters] 指定配置里定义的格式名清单 # [formatter_格式名] 为具体格式名的配置,里面有两个参数: # format - 定义输出日志的默认信息(前缀),可选的信息项包括: # %(levelno)s: 打印日志级别的数值 # %(levelname)s: 打印日志级别名称 # %(pathname)s: 打印当前执行函数所在文件的路径 # %(filename)s: 打印当前执行函数所在的文件名 # %(funcName)s: 打印日志的当前函数名 # %(lineno)d: 打印日志的当前行号 # %(asctime)s: 打印日志的时间 # %(millisecond)s: 打印日志的时间(毫秒,不适用于官方的logging) # %(thread)d: 打印线程ID # %(threadName)s: 打印线程名称 # %(process)d: 打印进程ID # %(message)s: 打印日志信息 # datefmt - 定义日期时间(asctime)的输出格式,默认为%Y-%m-%d %H:%M:%S,uuu # %y 两位数的年份表示(00-99) # %Y 四位数的年份表示(000-9999) # %m 月份(01-12) # %d 月内中的一天(0-31) # %H 24小时制小时数(0-23) # %I 12小时制小时数(01-12) # %M 分钟数(00=59) # %S 秒(00-59) # %a 本地简化星期名称 # %A 本地完整星期名称 # %b 本地简化的月份名称 # %B 本地完整的月份名称 # %c 本地相应的日期表示和时间表示 # %j 年内的一天(001-366) # %p 本地A.M.或P.M.的等价符 # %U 一年中的星期数(00-53)星期天为星期的开始 # %w 星期(0-6),星期天为星期的开始 # %W 一年中的星期数(00-53)星期一为星期的开始 # %x 本地相应的日期表示 # %X 本地相应的时间表示 # %Z 当前时区的名称 # %% %号本身 # 注意:python并未给出毫秒的占位符,因此如果datefmt为空输出格式才有毫秒,如果要自己输出,请采用%(millisecond)s占位符 ############################################### [formatters] keys=simpleFormatter [formatter_simpleFormatter] format=[%(asctime)s.%(millisecond)s][%(levelname)s][PID:%(process)d][TID:%(thread)d][FILE:%(filename)s][FUN:%(funcName)s]%(message)s datefmt=%Y-%m-%d %H:%M:%S ``` ## 通过队列日志句柄实现异步日志处理 可以通过simple_log.QueueHandler来实现异步日志处理,QueueHandler可将日志信息写入内存队列中,然后通过对内存队列的日志项进行处理来实现异步记录日志。 使用步骤如下: ### 1、配置Logger句柄,让日志写入队列中 传入的4个参数说明如下: - queue - 要写入的队列对象,如果不传值,代表由handler自行生成一个内存队列;如果传入的是字符串,则代表该字符串为可获取到队列对象的变量名字符串,handler将自动根据该字符串获取队列对象 - topic_name - 默认的日志主题,当写日志的时候没有传topicName的扩展信息时使用 - is_deal_msg - 是否处理日志格式,true代表直接生成完整的日志,将日志字符串写入队列;false代表不直接生成完整的日志消息,而是将record对象放入队列(待后面的程序自动处理) - error_queue_size - 通过start_logging方法写日志时,遇到异常时记录错误信息的队列大小, 如果错误信息数量超过大小,会自动删除前面的数据,0代表不限制大小 ``` # 对于json格式的日志配置,句柄配置参考如下 # 第1个配置会在写日志的时候直接将格式化后的日志字符串写入队列 # 第2个配置不直接格式化日志字符串,而是将包含日志所有信息的record对象送入队列,可在处理时再格式化 "handlers": { "QueueMsgHandler": { "class": "HiveNetCore.logging_hivenet.QueueHandler", "level": "DEBUG", "formatter": "simpleFormatter", "queue": "", "topic_name": "", "is_deal_msg": true, "error_queue_size": 20 }, "QueueRecordHandler": { "class": "HiveNetCore.logging_hivenet.QueueHandler", "level": "DEBUG", "formatter": "simpleFormatter", "queue": "", "topic_name": "", "is_deal_msg": false, "error_queue_size": 20 } } # 对于ini格式的日志配置,句柄配置参考如下 [QueueMsgHandler] class=HiveNetCore.logging_hivenet.handler_queueHandler level=DEBUG formatter=logstashFormatter args=("queue_var_name", "topic_name", true, 20) [QueueRecordHandler] class=HiveNetCore.logging_hivenet.handler_queueHandler level=DEBUG formatter=logstashFormatter args=("queue_var_name", "topic_name", false, 20) ``` ### 2、使用配置生成simple_log.Logger对象 ``` # 配置中的QueueMsg指定使用QueueMsgHandler queue_logger = simple_log.Logger(logger_name='QueueMsg', json_str=_LOGGER_QUEUE_MSG_JSON_STR) ``` 或 ``` # 配置中的QueueRecord指定使用QueueRecordHandler queue_logger = simple_log.Logger(logger_name='QueueRecord', json_str=_LOGGER_QUEUE_MSG_JSON_STR) ``` ### 3、按正常情况使用Logger记录日志 ``` # 记录日志,简单模式 queue_logger.log(simple_log.INFO, 'INFO msg') # 记录日志,通过extra传入特殊信息,示例中的topicName用于指定日志的topic_name;name2Obj为自定义传入对象,对于QueueRecordHandler的情况,可以自定义日志内容处理函数,通过record.name2Obj访问并使用对象生成日志内容 queue_logger.log( simple_log.ERROR, 'ERROR msg', extra={ 'topicName': 'name3', 'name2Obj': ['a', 'b', 'c', 'd'] } ) ``` ### 4、处理队列的日志信息 可以通过Logger.base_logger.handlers[0]获取handler对象,然后通过handler.queue访问队列,取到消息项进行相应的处理,消息项的格式如下: - queue_obj.levelno {int} : 日志级别,值说明参考logging.INFO这些值 - queue_obj.topic_name {string} : 日志主题标识(用于使用方区分日志来源),注意'default'为保留的标识名 - queue_obj.msg {string} : 已格式化后的日志内容, is_deal_msg为True时有效 - queue_obj.record {object} : 未格式化的日志相关信息,其中record.msg为要写入的日志'%(message)s', is_deal_msg为False时有效 logging.QueueHandler也提供了对队列日志自动处理的方法,通过handler.start_logging启动后端线程自动处理队列中的日志数据,相关参数的定义如下: ``` @param {dict} loggers_or_funs - 要写入的日志logger对象列表(或处理函数列表), key为topic_name, value为对应的处理日志类(HiveNetCore.logging_hivenet.Logger)或处理函数 其中'default'为默认日志处理类或处理函数,如果不传入则代表匹配不到的topic_name不进行处理,规则如下:遇到日志项的topic_name在列表中不存在,先找'default'的日志对象进行处理,如果传入的日志对象清单中没有'default',则不进行该日志项的处理 如果传入的是处理函数,格式为funs(levelno, topic_name, msg){...} 注意:如果为logger,则所传入的日志对象的formatter将统一修改为'%(message)s',因此该日志对象注意不要与其他日志处理共用;字典里可以支持logger和fun并存,只是如果存在fun的情况,应注意formatters的取值 @param {int} thread_num=1 - 处理队列对象的线程数 @param {dict} deal_msg_funs={} - 处理record的函数(形成msg部分内容), 当is_deal_msg为False时有效, key为topic_name, value为对应的日志内容生成函数,搜索函数的规则与loggers一样,处理函数的定义应如下:func(topic_name, record) {return msg} @param {dict} formatters=None - is_deal_msg为False时,用于格式化record的Formatter,key为topic_name,value为该topic_name的Formatter,搜索格式对象的规则与loggers一样,如果formatters=None,代表自动获取loggers的Formatter形成该字典 ``` 提醒:如果loggers_or_funs对应的处理对象为Logger,则会根据Logger的配置进行日志的实际输出处理;如果处理对象为函数,则执行函数进行自定义的日志处理(例如发送到远程服务器)