Python--Redis实战:第五章:使用Redis构建支持程序:第1节:使用Redis来记录日志

上一篇文章: Python--Redis实战:第四章:数据安全与性能保障:第8节:关于性能方面的注意事项
下一篇文章: Python--Redis实战:第五章:使用Redis构建支持程序:第2节:计数器和统计数据

在构建应用程序和服务的过程当中,对正在运行的系统的相关信息的挖掘能力将变得愈来愈重要:不管是经过挖掘信息来诊断系统问题,仍是发现系统中潜在的问题,甚至是挖掘与用户有关的信息:这些都须要用到日志。redis

在Linux和Unix的世界中,有两种常见的记录日志的方法。第一种是将日志记录到文件里面,而后随着时间流逝不断地将一个又一个日志添加到文件里面,并在一段时间以后建立新的日志文件。包括Redis在内的不少软件都使用这种方法来记录日志。但这种记录日志的方式有可能会赶上麻烦:由于每一个不一样的服务器会建立不一样的日志,而这些服务轮换日志也各不相同,而且也缺乏一种可以方便地聚合全部日志并对其进行处理的经常使用方法。segmentfault

syslog服务是第二种经常使用的日志记录方法,这个服务运行在几乎全部Linux服务器和Unix服务器的514号TCP端口和UDP端口上面。syslog接受其余程序发来的日志信息,并将这些消息路由存储在硬盘上的各个日志文件里面,除此以外,syslog还复制旧日志的轮换和删除工做。经过配置,syslog甚至能够将日志消息转发给其余服务来作进一步的处理。由于指定日志的轮换和删除工做都交给syslog来完成,因此使用syslog服务比直接将日志写入文件要方便的多。安全

替换syslog

不管读者使用上面列举的两种日志方法中的哪种,都最好考虑把系统目前的syslog守护进程(一般是Rsyslogd)替换成syslog-ng。由于我通过使用并配置Rsyslogd和syslog-ng以后,发现syslog-ng用于管理和组织日志消息的配置语言使用起来更简单一些。另外,尽管由于时间和篇幅限制,我没有办法在书中构建一个处理syslog消息并将消息存储到Redis里面的服务,但对于那些须要在处理请求时当即执行的操做,以及那些能够在请求处理完毕以后再执行的操做(如日志记录和更新计数器)来讲,这种服务器很是适合用做介于这两种操做之间的间接层。服务器

syslog的转发功能能够将不一样的日志分别存储在同一台服务器的多个文件里面,这对于长时间地记录日志很是有帮助(记得备份)。在这一节中,咱们将介绍如何使用Redis来存储于时间紧密相关的日志,从而在功能上替代那些须要在短时间内被存储的syslog消息。函数

首先让咱们来看看,如何记录连续更新的最新日志消息。性能

最新日志

在构建一个系统的时候,判断哪些信息须要被记录是一件困难的事情:须要记录用户的登陆和退出行为吗?须要记录用户修改帐号信息的时间吗?仍是只记录错误和异常就能够了?虽然我没有办法替你回答这些问题,但我能够向你提供一种将最新出现的日志消息以列表的形式存储到Redis里面的方法,这个列表能够帮助及你随时了解最新出现的日志都是什么样子的。debug

下面代码的log_recent()函数展现了将最新日志记录到Redis里面的方法:为了维持一个包含最新日志的列表,程序使用lpush命令将日志消息推入一个列表里面。以后,若是咱们想要查看已有日志消息的话,那么可使用lrange命令来取出列表中的消息。除了lpush以外,函数还加入了一些额外的代码,用于命名不一样的日志消息队列,并根据文意的严重性对日志进行分级,若是你以为本身并不须要这些附加功能的话,也能够将相关代码删除掉,只保留基本的日志添加功能。日志

#设置一个字典,将大部分日志的安全级别映射为字符串
import logging
import time

SEVERITY={
    logging.DEBUG:'debug',
    logging.INFO:'info',
    logging.WARNING:'warning',
    logging.ERROR:'debug',
    logging.CRITICAL:'critical',
}
SEVERITY.update((name,name) for name in SEVERITY.values())

def log_recent(conn,name,message,severity=logging.INFO,pipe=None):
    #尝试将日志的安全级别准还为简单的字符串
    severity=str(SEVERITY.get(severity,severity)).lower()
    #建立负责存储消息的键
    destination='recent:%s:%s'%(name,severity)
    #将当前时间添加到消息里面,用于记录消息的发送时间
    message=time.asctime()+'  '+message
    #使用流水线来将通讯往返次数下降为一次
    pipe=pipe or conn.pipeline()
    #将消息添加到日志列表的最前面
    pipe.lpush(destination,message)
    #对日志列表进行修建,让它只包含最新的100条消息
    pipe.ltrim(destination,0,99)
    #执行两个命令
    pipe.execute()

除了那些将日志的安全级别转换为字符串(如info和debug)的代码以外,log_recent()函数的定义很是简单:基本上就是一个lpush加上一个ltrim。如今你已经知道怎样记录最新出现的日志了,是时候来了解一下该如何记录最常出现(也是最重要的)日志消息了。code

常见日志

若是实际运行一下log_recent()函数的话,你就会发现,尽管log_recent()函数很是适用于记录当前发生的事情,但它并不擅长告诉你哪些消息时重要的,哪些消息是不重要的。为了解决这个问题,咱们可让程序记录特定消息出现的频率,并根据出现频率的高低来决定消息的排列顺序,从而帮助咱们找出最重要的消息。orm

下面代码的log_comon()函数展现了记录并轮询最多见日志消息的方法:程序会将消息做为成员存储的有序集合里面,并将消息出现的频率设置为成员的分值。为了确保咱们看见的常见消息都是最新的,程序会以每小时一次的频率对消息进行轮换,并在轮换日志的时候保留上一个小时记录的常见消息,从而防止没有任何消息存在的状况出现。

import logging
import time
from datetime import datetime

import redis

SEVERITY={
    logging.DEBUG:'debug',
    logging.INFO:'info',
    logging.WARNING:'warning',
    logging.ERROR:'debug',
    logging.CRITICAL:'critical',
}
SEVERITY.update((name,name) for name in SEVERITY.values())

def log_recent(conn,name,message,severity=logging.INFO,pipe=None):
    #尝试将日志的安全级别准还为简单的字符串
    severity=str(SEVERITY.get(severity,severity)).lower()
    #建立负责存储消息的键
    destination='recent:%s:%s'%(name,severity)
    #将当前时间添加到消息里面,用于记录消息的发送时间
    message=time.asctime()+'  '+message
    #使用流水线来将通讯往返次数下降为一次
    pipe=pipe or conn.pipeline()
    #将消息添加到日志列表的最前面
    pipe.lpush(destination,message)
    #对日志列表进行修建,让它只包含最新的100条消息
    pipe.ltrim(destination,0,99)
    #执行两个命令
    pipe.execute()

def log_common(conn,name,message,severity=logging.INFO,timeout=5):
    # 尝试将日志的安全级别准还为简单的字符串
    severity = str(SEVERITY.get(severity, severity)).lower()
    #负责存储近期的常见日志消息的键
    destination = 'common:%s:%s' % (name, severity)
    #由于程序每小时须要轮换一第二天志,因此它使用一个键来记录当前所处的小时数
    start_key=destination+':start'
    # 使用流水线来将通讯往返次数下降为一次
    pipe = conn.pipeline()
    end=time.time()+timeout
    while time.time()<end:
        try:
            #当记录当前小时数的键进行监视,确保轮换操做能够正确的执行
            pipe.watch(start_key)
            #取得当前时间
            now=datetime.utcnow().timetuple()
            #取得当前所处的小时数
            hour_start=datetime(*now[:4].isoformat())

            existing=pipe.get(start_key)
            #建立一个事务
            pipe.multi()
            #若是这个常见日志消息列表记录的是上个小时的日志。。
            if existing and existing<hour_start:
                #将这些旧的常见日志消息归档
                pipe.rename(destination,destination+':last')
                pipe.rename(start_key,destination+':pstart')
                #更新当前所处的小时数
                pipe.set(start_key,hour_start)
            elif not existing:
                pipe.set(start_key,hour_start)
            #对记录日志出现次数的计数器执行自增操做
            pipe.zincrby(destination,message)
            #log_recent()函数负责记录日志并调用execute()函数
            log_recent(pipe,name,message,severity,pipe)
            return
        except redis.exceptions.WatchError:
            continue

由于记录常见日志的函数须要当心地处理上一小时收集的日志,因此它比记录最新日志的函数要复杂的多:程序会在一个watch/multi/exec事务里面,对记录了上一小时的常见日志的有序集合进行更名,并对记录了当前所处小时数的键进行更新。除此以外,程序还会降流水线对象传递给log_recent()函数,以此来减小记录常见日志和记录最新日志时,客户端与Redis服务器之间的通讯往返次数。

经过最新日志和常见日志,咱们如今已经知道怎样将系统的运行信息存储到Redis里面了,那么还有什么其余信息是适合存储在Redis里面的呢?

上一篇文章: Python--Redis实战:第四章:数据安全与性能保障:第8节:关于性能方面的注意事项
下一篇文章: Python--Redis实战:第五章:使用Redis构建支持程序:第2节:计数器和统计数据
相关文章
相关标签/搜索