|
9 | 9 |
|
10 | 10 | syslog的转发功能可以将不同的日志分别存储在同一台服务器的多个文件里面,对于长时间地记录日志非常有帮助。我们可以使用redis来存储与时间紧密相关的日志,从而在功能上替代那些需要在短期内被存储的syslog消息。 |
11 | 11 |
|
12 | | -## (1)最新日志 ## |
| 12 | +## 1. 最新日志 ## |
13 | 13 |
|
14 | | -我们需要使用列表来存储最新日志文件,使用LPUSH命令将日志消息推入到列表中。如果我们之后想要查看已有日志消息的话,可以使用LRANGE命令来拉取列表中的消息。 |
| 14 | +我们需要使用 *“列表”* 来存储最新日志文件,使用LPUSH命令将日志消息推入到列表中。如果我们之后想要查看已有日志消息的话,可以使用LRANGE命令来拉取列表中的消息。 |
| 15 | + |
| 16 | +我们还要命名不同的日志消息队列,根据问题的严重性对日志进行分级。 |
| 17 | + |
| 18 | +``` |
| 19 | +import time |
| 20 | +import logging |
| 21 | +import unittest |
| 22 | +import redis |
| 23 | +from datetime import datetime |
| 24 | +
|
| 25 | +# 设置一个字典,将大部分日志的安全级别映射为字符串 |
| 26 | +SEVERITY = { |
| 27 | + logging.DEBUG: 'debug', |
| 28 | + logging.INFO: 'info', |
| 29 | + logging.WARNING: 'warning', |
| 30 | + logging.ERROR: 'error', |
| 31 | + logging.CRITICAL: 'critical', |
| 32 | +} |
| 33 | +
|
| 34 | +SEVERITY.update((name, name) for name in SEVERITY.values()) |
| 35 | +
|
| 36 | +""" |
| 37 | +存储最新日志文件,命名不同的日志消息队列,根据问题的严重性对日志进行分级 |
| 38 | +
|
| 39 | +@param {object} |
| 40 | +@param {string} name 消息队列名称 |
| 41 | +@param {string} message 消息 |
| 42 | +@param {string} severity安全级别 |
| 43 | +@param {object} pip pipline |
| 44 | +
|
| 45 | +""" |
| 46 | +def logRecent(conn, name, message, severity=logging.INFO, pip=None): |
| 47 | + # 将日志的安全级别转换为简单的字符串 |
| 48 | + severity = str(SEVERITY.get(severity, severity)).lower() |
| 49 | + # 创建要保存的redis列表key |
| 50 | + destination = 'recent:%s:%s'%(name, severity) |
| 51 | + # 将当前时间加到消息里面,用于记录消息的发送时间 |
| 52 | + message = time.asctime() + ' ' + message |
| 53 | + # 使用流水线来将通信往返次数降低为一次 |
| 54 | + pipe = pip or conn.pipeline() |
| 55 | + # 将消息添加到列表的最前面 |
| 56 | + pipe.lpush(destination, message) |
| 57 | + # 修剪日志列表,让它只包含最新的100条消息 |
| 58 | + pipe.ltrim(destination, 0, 99) |
| 59 | + pipe.execute() |
| 60 | +``` |
| 61 | + |
| 62 | +## 2. 常见日志 ## |
| 63 | + |
| 64 | +我们需要记录较高频率出现的日志,使用*“有序集合”*,将消息作为成员,消息出现的频率为成员的分值。 |
| 65 | + |
| 66 | +为了确保我们看到的常见消息都是最新的,需要以每小时一次的频率对消息进行轮换,并在轮换日志的时候保留上一个小时记录的常见消息,从而防止没有任何消息存储的情况出现。 |
| 67 | + |
| 68 | +``` |
| 69 | +""" |
| 70 | +记录较高频率出现的日志,每小时一次的频率对消息进行轮换,并在轮换日志的时候保留上一个小时记录的常见消息 |
| 71 | +
|
| 72 | +@param {object} |
| 73 | +@param {string} name 消息队列名称 |
| 74 | +@param {string} message 消息 |
| 75 | +@param {string} severity安全级别 |
| 76 | +@param {int} timeout 执行超时时间 |
| 77 | +
|
| 78 | +""" |
| 79 | +def logCommon(conn, name, message, severity=logging.INFO, timeout=5): |
| 80 | + # 设置日志安全级别 |
| 81 | + severity = str(SEVERITY.get(severity, severity)).lower() |
| 82 | + # 负责存储近期的常见日志消息的键 |
| 83 | + destination = 'common:%s:%s'%(name, severity) |
| 84 | + # 每小时需要轮换一次日志,需要记录当前的小时数 |
| 85 | + start_key = destination + ':start' |
| 86 | + pipe = conn.pipeline() |
| 87 | + end = time.time() + timeout |
| 88 | + while time.time() < end: |
| 89 | + try: |
| 90 | + # 对记录当前小时数的键进行监听,确保轮换操作可以正常进行 |
| 91 | + pipe.watch(start_key) |
| 92 | + # 当前时间 |
| 93 | + now = datetime.utcnow().timetuple() |
| 94 | + # 取得当前所处的小时数 |
| 95 | + hour_start = datetime(*now[:4]).isoformat() |
| 96 | +
|
| 97 | + existing = pipe.get(start_key) |
| 98 | + # 开始事务 |
| 99 | + pipe.multi() |
| 100 | + # 如果这个常见日志消息记录的是上个小时的日志 |
| 101 | + if existing and existing < hour_start: |
| 102 | + # 将这些旧的常见日志归档 |
| 103 | + pipe.rename(destination, destination + ':last') |
| 104 | + pipe.rename(start_key, destination + ':pstart') |
| 105 | + # 更新当前所处的小时数 |
| 106 | + pipe.set(start_key, hour_start) |
| 107 | + elif not existing: |
| 108 | + pipe.set(start_key, hour_start) |
| 109 | +
|
| 110 | + # 记录日志出现次数 |
| 111 | + pipe.zincrby(destination, message) |
| 112 | + # 将日志记录到日志列表中,调用excute |
| 113 | + logRecent(pipe, name, message, severity, pipe) |
| 114 | + return |
| 115 | + except redis.exceptions.WatchError: |
| 116 | + continue |
| 117 | +``` |
| 118 | + |
| 119 | +** 测试 ** |
| 120 | +测试代码如下: |
| 121 | + |
| 122 | +``` |
| 123 | +class TestLog(unittest.TestCase): |
| 124 | + def setUp(self): |
| 125 | + import redis |
| 126 | + self.conn = redis.Redis(db=15) |
| 127 | + self.conn.flushdb |
| 128 | +
|
| 129 | + def tearDown(self): |
| 130 | + self.conn.flushdb() |
| 131 | + del self.conn |
| 132 | + print |
| 133 | + print |
| 134 | +
|
| 135 | + def testLogRecent(self): |
| 136 | + import pprint |
| 137 | + conn = self.conn |
| 138 | +
|
| 139 | + print "Let's write a few logs to the recent log" |
| 140 | + for msg in xrange(5): |
| 141 | + logRecent(conn, 'test', 'this is message %s'%msg) |
| 142 | +
|
| 143 | + recent = conn.lrange('recent:test:info', 0, -1) |
| 144 | + print 'The current recent message log has this many message:', len(recent) |
| 145 | + print 'Those message include:' |
| 146 | + pprint.pprint(recent[:10]) |
| 147 | + self.assertTrue(len(recent) >= 5) |
| 148 | +
|
| 149 | + def testLogCommon(self): |
| 150 | + import pprint |
| 151 | + conn = self.conn |
| 152 | +
|
| 153 | + print "Let's writ a few logs to the common log" |
| 154 | + for count in xrange(1, 6): |
| 155 | + for i in xrange(count): |
| 156 | + logCommon(conn, 'test', 'message-%s'%count) |
| 157 | +
|
| 158 | + common = conn.zrevrange('common:test:info', 0, -1, withscores=True) |
| 159 | + print 'The current common message log has this many message:', len(common) |
| 160 | + print 'Those common message include:' |
| 161 | + pprint.pprint(common) |
| 162 | + self.assertTrue(len(common) >= 5) |
| 163 | +
|
| 164 | +if __name__ == '__main__': |
| 165 | + unittest.main() |
| 166 | +``` |
15 | 167 |
|
16 | | -我们还要命名不同的日志消息对垒,根据问题的严重性对日志进行分级。 |
|
0 commit comments