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節:計數器和統計數據
相關文章
相關標籤/搜索