原文:http://blog.csdn.net/largetalk/article/details/8640854
1. Sentry介紹及使用
Sentry is a realtime event logging and aggregation platform. At its core it specializes in monitoring errors and extracting all the information needed to do a proper post-mortem without any of the hassle of the standard user feedback loop.
Sentry 是一個實時事件日志記錄和匯集的平台。其專注於錯誤監控以及提取一切事后處理所需信息而不依賴於麻煩的用戶反饋。
1.1 Sentry介紹
無論測試如何完善的程序,bug總是免不了會存在的,有些bug不是每次都會出現,測試時運行好好的代碼可能在某個用戶使用時就歇菜了,可是當程序在用戶面前崩潰時,你是看不到錯誤的,當然你會說:”Hey, 我有記日志呢”。 但是說實話,程序每天每時都在產生大量的日志,而且分布在各個服務器上,並且如果你有多個服務在維護的話,日志的數量之多你是看不過來的吧。等到某天某個用戶實在受不了了,打電話來咆哮的時候,你再去找日志你又會發現日志其實沒什么用:缺少上下文,不知道用戶什么操作導致的異常,異常太多(從不看日志的緣故)不知如何下手 等等。
Sentry就是來幫我們解決這個問題的,它是一款精致的Django應用,目的在於幫助開發人員從散落在多個不同服務器上毫無頭緒的日志文件里發掘活躍的異常,繼而找到潛在的臭蟲。
Sentry是一個日志平台, 它分為客戶端和服務端,客戶端(目前客戶端有Python, PHP,C#, Ruby等多種語言)就嵌入在你的應用程序中間,程序出現異常就向服務端發送消息,服務端將消息記錄到數據庫中並提供一個web節目方便查看。Sentry由python編寫,源碼開放,性能卓越,易於擴展,目前著名的用戶有Disqus, Path, mozilla, Pinterest等。
1.3 Sentry權限介紹
Sentry目前用戶類型有四種: 超級管理員, 管理員,普通用戶和System agents. 超級用戶只能通過命令行來創建,其他用戶可以自己注冊或由其他用戶邀請注冊加入,然后由超級管理員或管理員分配項目和權限。為了更好支持團隊協助以及信息安全,新版本Sentry(5.4.2)經過了重新設計,重新設計后的Sentry以Team為中心組織權限。所謂Team就是一個團隊,一些用戶組織在一起對某些項目有操作權限的組織。一個項目只能屬於一個Team, 一個用戶卻可以屬於多個Team, 並可在不同Team中扮演不同角色, 如用戶A在Team X是管理員而在Team Y中是System agents. Sentry對用戶角色的指定只能到Team級別,不能到Project級別, 所以將某個用戶加入到某個Team之后,這個用戶就對所有所有屬於這個Team下所有project有了相同的權限。
Sentry的如此設計雖說不夠細致,但我們使用時只要遵照現實世界的情況來划分Team和project即可。比如我們目前有一個團隊,這個團隊負責兩個項目,我們在Sentry上就建立一個Team A和屬於這個Team的project a和project b. 當在使用過程中我們發現project b的等級較高,不能讓所有人都可以看到信息,我們可以建立一個虛擬Team B,然后將project b的team 改Team B, 然后再給Team B添加用戶和相應權限即可. Sentry對team的owner, project的owner, project屬於的team 等等實體關系的修改都極為簡單,沒有副作用。
下面簡單介紹下Sentry中各個類型用戶的權限。
- 超級管理員: 能創建各種用戶, team和project只能由超級管理員創建。項目的一些設置比如改變Owner, 數據公開可見與否(設為public的數據可以通過url不登陸也能查看)以及客戶端domain限制的設定。另外還有管理項目的api key(客戶端只有得到此api key才能向Sentry發送消息)的權限等等。
- 管理員: 能創建用戶, team和項目設定中除改變owner之外的權限, 可以對項目中具體數據做resolve, bookmark, public/public和remove操作。
- 普通用戶: 無Team界面,只能對項目中具體數據做resolve, bookmark, public/unpublic和remove操作。
- System agents: 無Team界面,只能對項目中具體數據做bookmark, unpublic和remove操作。
此外,還需知曉Sntry中team和project的Onwer概念,只有是team或project的owner(除超級管理員外)才能更改其owner, 一旦成為team或project的Owner,則獲得對此team或project如同超級管理員般的權限。
Sentry的權限和實體設置基本可以滿足現實的需求:
- 如果一個團隊負責多個項目,可以通過一個Team多個項目方式來實現。
- 如果一個人參與多個團隊, 可以將該用戶添加到多個team中去。
- 一個team或project設置相應的Owner,則可以由此人負責該team或project內的活動。(唯一的缺點是team owner不可以創建project)
- 一個team或項目的分工應該是有一個管理者來設定項目的基本設定,管理api key, 並將api key分發給項目開發者,項目開發者以普通用戶身份登陸Sentry查看錯誤和resolve錯誤, 運維以System agents身份登陸Sentry查看系統運行狀態。
1.4 Sentry性能
有些人擔心Sentry會影響程序性能,所以我搭建了一個Sentry服務器做了下測試。
我一共使用了兩台機器(機器是HP ProLLiant DL360 G4p)作為服務器, 一台安裝了Sentry和Mysql, 一台安裝了memcache和redis, 然后在sentry.conf.py里將QUEUE和CACHE都打開。使用supervisor啟了三個服務
sentry start http
sentry start udp
sentry celeryd -B
首先測試下直接插入消息的效率如何:
from raven import Client from timeit import timeit try: import cProfile as profile except ImportError: import profile from pstats import Stats client = Client('udp://11c5f6e203cb4b228b2dcac3df29dad2:55adfa2e6d8c4fd799694d8dcde372c3@172.16.97.11:9001/2') def do_capture_msg2(): for i in range(1000): client.captureMessage(str(i) + 'hello world! from do_capture_msg2') def test(f, number=1000): time = timeit(f, number=number) st = Stats(profile.Profile().runctx('f()', globals(), locals())) print("funcation_name msec rps tcalls funcs") print("%-11s %6.0f %6.0f %7d %6d" % (f.__name__, 1000 * time, number / time, st.total_calls, len(st.stats))) test(do_capture_msg2, number=10)
測試結果:
funcation_name msec rps tcalls funcs
do_capture_msg2 4093 2 287004 101
我一共提交了10000個消息,共耗時4093ms, 每次消息耗時很少,這里有兩個需要注意的地方,一個是我使用的是udp接口,如果使用http接口,這個耗時會增加不少。對於udp的傳輸非可靠性,我認為在內網環境內應該是基本可靠的,在測試過程中,未發現消息遺漏的情況。另一個需要注意的地方是由於我的服務器性能不高,這10000個消息並不是在這4s中內就被處理完了,而是被記錄到任務隊列了,所以在服務器可以看到,客戶端的請求完成之后,服務器的cpu使用率還需要一段時間才能降下來。
普通消息的提交處理非常快,那對於異常消息又如何:
def do_capture_exc(): try: 1 / 0 except ZeroDivisionError: client.captureException() test(do_capture_exc, number=1000)
測試結果:
funcation_name msec rps tcalls funcs
do_capture_exc 1061 942 1029 121
略慢,但我估計異常的拋出和捕獲才是元凶
下面結合應用程序來測試性能,創建一個django程序,添加一個非常簡單的view響應函數:
def sentry(request): t = random.randint(1, 10) time.sleep(t/1000.0) u = User.objects.all()[0] return HttpResponse('user %s say hello world from django: %sms '%(u.username, t))
啟動腳本是:
#!/bin/bash
gunicorn_django -k gevent -w 8 --worker-connections 3000 -b 0.0.0.0:8001 -D
ab測試結果是:
ab -n 1000 -c 100 http://172.16.97.12:8001/
Server Software: gunicorn/0.17.2
Server Hostname: 172.16.97.12
Server Port: 8001
Document Path: /sentry
Document Length: 45 bytes
Concurrency Level: 100
Time taken for tests: 2.298 seconds
Complete requests: 1000
Failed requests: 90
(Connect: 0, Receive: 0, Length: 90, Exceptions: 0)
Write errors: 0
Total transferred: 185090 bytes
HTML transferred: 45090 bytes
Requests per second: 435.11 [#/sec] (mean)
Time per request: 229.827 [ms] (mean)
Time per request: 2.298 [ms] (mean, across all concurrent requests)
Transfer rate: 78.65 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.7 0 9
Processing: 16 219 149.6 180 823
Waiting: 16 219 149.6 180 823
Total: 16 219 149.9 180 825
將view函數改為如下:
def sentry(request): t = random.randint(1, 10) time.sleep(t/1000.0) u = User.objects.all()[0] client.captureMessage('user %s say hello world from django: %sms '%(u.username, t)) return HttpResponse('user %s say hello world from django: %sms '%(u.username, t))
ab測試結果如下:
ab -n 1000 -c 100 http://172.16.97.12:8001/sentry
Server Software: gunicorn/0.17.2
Server Hostname: 172.16.97.12
Server Port: 8001
Document Path: /sentry
Document Length: 45 bytes
Concurrency Level: 100
Time taken for tests: 3.175 seconds
Complete requests: 1000
Failed requests: 90
(Connect: 0, Receive: 0, Length: 90, Exceptions: 0)
Write errors: 0
Total transferred: 185090 bytes
HTML transferred: 45090 bytes
Requests per second: 314.95 [#/sec] (mean)
Time per request: 317.510 [ms] (mean)
Time per request: 3.175 [ms] (mean, across all concurrent requests)
Transfer rate: 56.93 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.6 0 8
Processing: 17 301 225.2 241 1308
Waiting: 17 301 225.2 241 1307
Total: 17 302 225.5 241 1310
1000次請求運行時間從2.298s變為3.175s, rps從435.11降為314.95