资讯专栏INFORMATION COLUMN

使用Redis记录系统日志

ityouknow / 3014人阅读

摘要:使用记录系统日志在构建一个系统时,我们常常需要记录当前发生的事情,以及记录特定消息出现的频率,根据出现频率的高低来决定消息的排列信息,帮助我们找到重要的信息。常见记录日志的方法有两种将日志记录在文件中。

使用Redis记录系统日志

在构建一个系统时,我们常常需要记录当前发生的事情,以及记录特定消息出现的频率,根据出现频率的高低来决定消息的排列信息,帮助我们找到重要的信息。

常见记录日志的方法有两种:

将日志记录在文件中。随时时间流逝将日志行不断添加到文件里面,并在一段时间后创建新的日志文件。这种方式为每个不同的服务创建不同的日志,由于服务轮换日志的机制不同,也缺少一种能够方便地聚合所有日志并对其进行处理的常见方法。

syslog服务。这种服务几乎运行在Linux服务器和Unix服务器的514号TCP端口和UDP端口上。syslog接受其他程序发来的日志消息,并将这个消息路由至存储在硬盘上的各个日志文件,并且负责旧日志的轮换和删除工作。甚至还可以将日志消息转发给其他服务来做进一步的处理。

syslog的转发功能可以将不同的日志分别存储在同一台服务器的多个文件里面,对于长时间地记录日志非常有帮助。我们可以使用redis来存储与时间紧密相关的日志,从而在功能上替代那些需要在短期内被存储的syslog消息。

1. 最新日志

我们需要使用 “列表” 来存储最新日志文件,使用LPUSH命令将日志消息推入到列表中。如果我们之后想要查看已有日志消息的话,可以使用LRANGE命令来拉取列表中的消息。

我们还要命名不同的日志消息队列,根据问题的严重性对日志进行分级。

import time
import logging
import unittest
import redis
from datetime import datetime

# 设置一个字典,将大部分日志的安全级别映射为字符串
SEVERITY = {
    logging.DEBUG: "debug",
    logging.INFO: "info",
    logging.WARNING: "warning",
    logging.ERROR: "error",
    logging.CRITICAL: "critical",
}

SEVERITY.update((name, name) for name in SEVERITY.values())

"""
存储最新日志文件,命名不同的日志消息队列,根据问题的严重性对日志进行分级

@param {object}
@param {string} name    消息队列名称
@param {string} message 消息
@param {string} severity安全级别
@param {object} pip     pipline

"""
def logRecent(conn, name, message, severity=logging.INFO, pip=None):
    # 将日志的安全级别转换为简单的字符串
    severity = str(SEVERITY.get(severity, severity)).lower()
    # 创建要保存的redis列表key
    destination = "recent:%s:%s"%(name, severity)
    # 将当前时间加到消息里面,用于记录消息的发送时间
    message = time.asctime() + " " + message
    # 使用流水线来将通信往返次数降低为一次
    pipe = pip or conn.pipeline()
    # 将消息添加到列表的最前面
    pipe.lpush(destination, message)
    # 修剪日志列表,让它只包含最新的100条消息
    pipe.ltrim(destination, 0, 99)
    pipe.execute()
2. 常见日志

我们需要记录较高频率出现的日志,使用“有序集合”,将消息作为成员,消息出现的频率为成员的分值。

为了确保我们看到的常见消息都是最新的,需要以每小时一次的频率对消息进行轮换,并在轮换日志的时候保留上一个小时记录的常见消息,从而防止没有任何消息存储的情况出现。

"""
记录较高频率出现的日志,每小时一次的频率对消息进行轮换,并在轮换日志的时候保留上一个小时记录的常见消息

@param {object}
@param {string} name    消息队列名称
@param {string} message 消息
@param {string} severity安全级别
@param {int}    timeout 执行超时时间

"""
def logCommon(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)
            # 将日志记录到日志列表中,调用excute
            logRecent(pipe, name, message, severity, pipe)
            return
        except redis.exceptions.WatchError:
            continue

测试
测试代码如下:

class TestLog(unittest.TestCase):
    def setUp(self):
        import redis
        self.conn = redis.Redis(db=15)
        self.conn.flushdb

    def tearDown(self):
        self.conn.flushdb()
        del self.conn
        print
        print

    def testLogRecent(self):
        import pprint
        conn = self.conn

        print "Let"s write a few logs to the recent log"
        for msg in xrange(5):
            logRecent(conn, "test", "this is message %s"%msg)

        recent = conn.lrange("recent:test:info", 0, -1)
        print "The current recent message log has this many message:", len(recent)
        print "Those message include:"
        pprint.pprint(recent[:10])
        self.assertTrue(len(recent) >= 5)

    def testLogCommon(self):
        import pprint
        conn = self.conn

        print "Let"s writ a few logs to the common log"
        for count in xrange(1, 6):
            for i in xrange(count):
                logCommon(conn, "test", "message-%s"%count)

        common = conn.zrevrange("common:test:info", 0, -1, withscores=True)
        print "The current common message log has this many message:", len(common)
        print "Those common message include:"
        pprint.pprint(common)
        self.assertTrue(len(common) >= 5)

if __name__ == "__main__":
    unittest.main()

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/40910.html

相关文章

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

    摘要:包括在内的很多软件都使用这种方法来记录日志。在这一节中,我们将介绍如何使用来存储于时间紧密相关的日志,从而在功能上替代那些需要在短期内被存储的消息。 上一篇文章:Python--Redis实战:第四章:数据安全与性能保障:第8节:关于性能方面的注意事项下一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第2节:计数器和统计数据 在构建应用程序和服务的过程中...

    mdluo 评论0 收藏0
  • SegmentFault 技术周刊 Vol.37 - 分布式缓存利器:Redis

    摘要:持久化到中反向代理的负载均衡基于的集群搭建如何实现从中订阅消息转发到客户端的扩展是阻塞式,使用订阅发布模式时,会导致整个进程进入阻塞。缓存是用于解决高并发场景下系统的性能及稳定性问题的银弹。 showImg(https://segmentfault.com/img/bVYE6k?w=900&h=385); Redis 是由意大利程序员 Salvatore Sanfilippo(昵称:a...

    binaryTree 评论0 收藏0

发表评论

0条评论

ityouknow

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<