用tornado搭建日志服务器

对于一个分布式应用,一个计算节点上可能产生一些日志,而为了便于将这些节点日志收集并分析,我们可以选择除了在本地生成日志外,还将某些级别的日志发送到日志服务器。

Python的logging模块可以用来对日志进行一些操作,其接口和著名的Java日志库log4j类似。logging.handlers支持一系列的日志处理方式,其中发送到日志服务器可以通过SocketHandler或者HttpHandler,不过本文既然选择了tornado,还是使用HttpHandler的方式。

首先,是分布式节点的部分。首先,我们通过get_logger方法得到logger对象,并向其添加HttpHandler。

import logging

logging_host = '127.0.0.1'
logging_port = 8888
logging_add_url = '/log/add/'

def get_logger():
    logger = logging.getLogger()
    
    http_handler = logging.handlers.HTTPHandler(
        '%s:%s' % (logging_host, logging_port),
        logging_add_url,
        method='POST'
    )
    http_handler.setLevel(logging.ERROR)
    logger.addHandler(http_handler)
    
    return logger

代码还是很简单的,HTTPHandler接受三个参数,一个是服务器的host,日志发送到的url,以及是GET还是POST方式。这里,省去了添加本地日志的过程。

对于服务器端来说,我们同样需要一个logger对象来接收通过Http获取到的日志。这里,我们将获取的日志记录在本地文件中,所以用到了FileHandler。代码如下:

def get_logger(filename):
    logger = logging.getLogger('monitor')
    filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'monitor.log')
    handler = logging.FileHandler(filename)
    formatter = logging.Formatter(
        '%(asctime)s - %(module)s.%(funcName)s.%(lineno)d - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    handler.setLevel(logging.ERROR)
    logger.addHandler(handler)
    return logger

值得注意的是,Python的logging模块对于每个要记录的日志,实际上是生成了一个LogRecord对象,而我们可以使用makeLogRecord方法来手动生成这个对象。因此,tornado处理日志的handler代码如下:

class LogAddHandler(tornado.web.RequestHandler):
    tuple_reg = re.compile("^\([^\(\)]*\)$")
    float_reg = re.compile("^\d*\.\d+$")
    int_reg = re.compile("^\d+$")
    
    def _extract(self, string):
        '''
        由于通过request.arguments的值join起来的仍然是个字符串,这里我们需要将其转化为Python对象
        通过分析,我们可以知道这个对象只能是tuple、float和int
        简单的来说,这个地方可以使用eval方法,但是通常情况下,"eval is evil"
        所以这里通过正则的方法进行解析
        '''
        if self.tuple_reg.match(string):
            # 这里用json.loads来加载一个JS的数组方式来解析Python元组,将前后的括号专为方括号
            # JS里的None为null,这样得到一个Python list,再转化为元组
            return tuple(json.loads('[%s]' % string[1: -1].replace('None', 'null')))
        elif self.float_reg.match(string):
            return float(string)
        elif self.int_reg.match(string):
            return int(string)
        return string
    
    def post(self):
        # tornado通过request.arguments可以得到一个字典
        # 每个字典的key是一个参数,而字典的value确是一个list,列表里是这些值
        # 例如POST过来a=1,则request.arguments得到的是{'a': ['1']}
        # 所以这里用join方法来把这些值连接起来
        args = dict(
            [(k, self._extract(''.join(v))) for (k, v) in self.request.arguments.iteritems()]
        )
        # makeLogRecord接受一个字典作为参数
        record = logging.makeLogRecord(args)
        # logger通过get_logger方法获取
        logger.handle(record)

代码里有对应的注释来解释代码。这样,我们只要在tornado里将“/log/add/”这个url对应这个handler即可。

至此搭建tornado日志服务就算完成了。当然我们还可以将其显示到网页上,方便远程浏览器查看。

本文的最后,推荐几个开源的日志项目,它们来自微博里的讨论。感谢@LJ_数据挖掘与社交网络计算等人提供:fluentdsentrymongodb-log

标签

赞这篇文章

分享到

5个评论

给作者留言

关于作者

残阳似血(@秦续业),程序猿一枚,把梦想揣进口袋的挨踢工作者。现加入阿里云,研究僧毕业于上海交通大学软件学院ADC实验室。熟悉分布式数据分析(DataFrame并行化框架)、基于图模型的分布式数据库和并行计算、Dpark/Spark以及Python web开发(Django、tornado)等。

博客分类

点击排行

标签云

扫描访问

主题

残阳似血的微博