Skip to content

Commit 89397be

Browse files
committed
记录常见日志
1 parent 5576c97 commit 89397be

2 files changed

Lines changed: 280 additions & 5 deletions

File tree

5_support_program/log.md

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,159 @@
99

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

12-
## (1)最新日志 ##
12+
## 1. 最新日志 ##
1313

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+
```
15167

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

5_support_program/log.py

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# !/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4+
import time
5+
import logging
6+
import unittest
7+
import redis
8+
from datetime import datetime
9+
410
# 设置一个字典,将大部分日志的安全级别映射为字符串
511
SEVERITY = {
612
logging.DEBUG: 'debug',
@@ -10,7 +16,125 @@
1016
logging.CRITICAL: 'critical',
1117
}
1218

13-
# 将日志的安全级别转换为简单的字符串
1419
SEVERITY.update((name, name) for name in SEVERITY.values())
1520

16-
def log_recent
21+
"""
22+
存储最新日志文件,命名不同的日志消息队列,根据问题的严重性对日志进行分级
23+
24+
@param {object}
25+
@param {string} name 消息队列名称
26+
@param {string} message 消息
27+
@param {string} severity安全级别
28+
@param {object} pip pipline
29+
30+
"""
31+
def logRecent(conn, name, message, severity=logging.INFO, pip=None):
32+
# 将日志的安全级别转换为简单的字符串
33+
severity = str(SEVERITY.get(severity, severity)).lower()
34+
# 创建要保存的redis列表key
35+
destination = 'recent:%s:%s'%(name, severity)
36+
# 将当前时间加到消息里面,用于记录消息的发送时间
37+
message = time.asctime() + ' ' + message
38+
# 使用流水线来将通信往返次数降低为一次
39+
pipe = pip or conn.pipeline()
40+
# 将消息添加到列表的最前面
41+
pipe.lpush(destination, message)
42+
# 修剪日志列表,让它只包含最新的100条消息
43+
pipe.ltrim(destination, 0, 99)
44+
pipe.execute()
45+
46+
"""
47+
记录较高频率出现的日志,每小时一次的频率对消息进行轮换,并在轮换日志的时候保留上一个小时记录的常见消息
48+
49+
@param {object}
50+
@param {string} name 消息队列名称
51+
@param {string} message 消息
52+
@param {string} severity安全级别
53+
@param {int} timeout 执行超时时间
54+
55+
"""
56+
def logCommon(conn, name, message, severity=logging.INFO, timeout=5):
57+
# 设置日志安全级别
58+
severity = str(SEVERITY.get(severity, severity)).lower()
59+
# 负责存储近期的常见日志消息的键
60+
destination = 'common:%s:%s'%(name, severity)
61+
# 每小时需要轮换一次日志,需要记录当前的小时数
62+
start_key = destination + ':start'
63+
pipe = conn.pipeline()
64+
end = time.time() + timeout
65+
while time.time() < end:
66+
try:
67+
# 对记录当前小时数的键进行监听,确保轮换操作可以正常进行
68+
pipe.watch(start_key)
69+
# 当前时间
70+
now = datetime.utcnow().timetuple()
71+
# 取得当前所处的小时数
72+
hour_start = datetime(*now[:4]).isoformat()
73+
74+
existing = pipe.get(start_key)
75+
# 开始事务
76+
pipe.multi()
77+
# 如果这个常见日志消息记录的是上个小时的日志
78+
if existing and existing < hour_start:
79+
# 将这些旧的常见日志归档
80+
pipe.rename(destination, destination + ':last')
81+
pipe.rename(start_key, destination + ':pstart')
82+
# 更新当前所处的小时数
83+
pipe.set(start_key, hour_start)
84+
elif not existing:
85+
pipe.set(start_key, hour_start)
86+
87+
# 记录日志出现次数
88+
pipe.zincrby(destination, message)
89+
# 将日志记录到日志列表中,调用excute
90+
logRecent(pipe, name, message, severity, pipe)
91+
return
92+
except redis.exceptions.WatchError:
93+
continue
94+
95+
"""
96+
单元测试
97+
"""
98+
class TestLog(unittest.TestCase):
99+
def setUp(self):
100+
import redis
101+
self.conn = redis.Redis(db=15)
102+
self.conn.flushdb
103+
104+
def tearDown(self):
105+
self.conn.flushdb()
106+
del self.conn
107+
print
108+
print
109+
110+
def testLogRecent(self):
111+
import pprint
112+
conn = self.conn
113+
114+
print "Let's write a few logs to the recent log"
115+
for msg in xrange(5):
116+
logRecent(conn, 'test', 'this is message %s'%msg)
117+
118+
recent = conn.lrange('recent:test:info', 0, -1)
119+
print 'The current recent message log has this many message:', len(recent)
120+
print 'Those message include:'
121+
pprint.pprint(recent[:10])
122+
self.assertTrue(len(recent) >= 5)
123+
124+
def testLogCommon(self):
125+
import pprint
126+
conn = self.conn
127+
128+
print "Let's writ a few logs to the common log"
129+
for count in xrange(1, 6):
130+
for i in xrange(count):
131+
logCommon(conn, 'test', 'message-%s'%count)
132+
133+
common = conn.zrevrange('common:test:info', 0, -1, withscores=True)
134+
print 'The current common message log has this many message:', len(common)
135+
print 'Those common message include:'
136+
pprint.pprint(common)
137+
self.assertTrue(len(common) >= 5)
138+
139+
if __name__ == '__main__':
140+
unittest.main()

0 commit comments

Comments
 (0)