python - 定時拆分備份 nginx 日誌

python - 定時拆分備份 nginx 日誌

一、背景:

nginx 的log 不會自動按天備份,而且記錄時間格式不統一,此程序專門解決這兩個問題;

二、windows 部署方式

1.在 nginx 目錄,創建一個 nginx_logs_backup.bat 文件;文件內容如下
    python nginx_logs_splter.py --nginxConf=nginx.conf --nginxDir=xxxxx --logPrefixs=access,error
2.在定時任務中加一個定時任務,調用這個 bat 文件;
    2.1 開始-程序-管理工具-任務計劃程序;
    2.2 新建基本任務;
    2.3 注意的一點是,在"編輯操作"窗口,在"起始於(可選)"這一欄需要填入 bat 所在目錄,否則 bat 不會執行;

三、執行邏輯

1.將指定前綴的 log 在同目錄創建一個臨時文件(對源文件重命名),如:access_200426.log;
2.使用 nginx -s 命令,從容重啓 nginx,重新創建 log;
3.讀 access_200426.log 文件,將記是 2020-04-26 產生的日誌,轉存至 ./bac/access_200426.log 文件中;
4.刪除臨時文件 access_200426.log ;
注:同一天可多次執行,轉存的 log 將增量添加;

四、調用方式

python nginx_logs_splter.py --nginxConf=nginx.conf --nginxDir=xxxxx --logPrefixs=access,error
參數:
    nginxConf=nginx 配置文件
    nginxDir=nginx 目錄
    logPrefixs=log文件前綴(多個逗號分隔)

五、nginx_logs_splter.py 源碼

#!/usr/bin/env python3
# coding=utf-8
import os
import sys
import argparse
import codecs
import time,datetime
import re

'''
拆分 nginx access log
日誌不會自動按天創建,需要輔助任務把日誌按天拆分備份,統一日誌時間格式;
作者:草青工作室
'''

_version='200426.1'
_isDebug = True
_isDebug = False

def logSpliter(nginxDir, prefix):
    #今日
    today = datetime.datetime.now();
    yymmdd_today = today.strftime('%y%m%d')
    #昨日
    yestoday = datetime.date.today()-datetime.timedelta(days=1)
    yymmdd_yestoday = yestoday.strftime('%y%m%d')

    # logFileFullName = os.path.join(nginxDir,"logs","%s.log"%prefix)
    tmpFileFullName = os.path.join(nginxDir,"logs","%s_%s.log"%(prefix,yymmdd_yestoday))
    bacFileFullName = os.path.join(nginxDir,"logs","bac","%s-%s.log"%(prefix,yymmdd_yestoday))

    print('%s\ntmpFileFullName=%s\nbacFileFullName=%s\n\n'%(
        '-'*60,
        tmpFileFullName,
        bacFileFullName))

    start = datetime.datetime.now()
    totalCount = 0
    with codecs.open(tmpFileFullName, 'r', 'utf-8') as f:
        for line in f.readlines():
            totalCount += 1
    print('總記錄數\t%s\tfileName=%s' % (totalCount,tmpFileFullName))
    # 針對 access log 的時間格式
    dtAccess = re.compile('\d{1,2}/[a-zA-Z]+/\d{4}:\d{1,2}:\d{1,2}:\d{1,2}')
    # 針對 error log 的時間格式
    dtError = re.compile('\d{4}/\d{1,2}/\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}')
    # 轉換 access log 日期格式("24/Apr/2020:23:26:29 +0800" to 2020-04-24 23:26:29)
    dtReplace = re.compile('^".+?"|^\[.+?\]')
    # 增量寫備份文件
    outputFile = open(bacFileFullName, 'a+', encoding='utf-8')
    # 寫備註
    outputFile.writelines("#備份時間\t%s\n" % today.strftime('%Y-%m-%d %H:%M:%S'))
    outputFile.writelines("#版本號\t%s\n" % _version)
    #轉存 tmp 文件
    with open(tmpFileFullName, 'r', encoding='utf-8') as f:
        rows = 0
        # 按行統計
        while True:
            rows += 1
            if rows % 10000 == 0:
                print('已分析\t%s/%s\t耗時\t%ss' % (rows
                                               ,totalCount
                                               ,(datetime.datetime.now() - start).seconds))
            # ------
            if _isDebug and rows>=35000:
                print('_isDebug = ',_isDebug)
                break
            # ------
            line = f.readline()
            if not line:      #等價於if line == "":
                break
            if line.startswith('#'):
                print("跳過註釋內容=>",line)
                continue
            #時間格式適配
            dt = None
            if 'access' in prefix:
                #獲取時間 "24/Apr/2020:14:43:38 +0800"
                arr = dtAccess.findall(line)
                if len(arr) == 0:
                    continue
                dt = datetime.datetime.strptime(arr[0],'%d/%b/%Y:%H:%M:%S')
                #轉換時間格式
                line = dtReplace.sub('"%s"'%dt.strftime('%Y-%m-%d %H:%M:%S'),line)
            elif 'error' in prefix:
                #獲取時間 2020/04/24 23:37:46
                arr = dtError.findall(line)
                if len(arr) == 0:
                    continue
                dt = datetime.datetime.strptime(arr[0],'%Y/%m/%d %H:%M:%S')
            if not dt:
                print('日期轉換失敗 dt is none')
                continue
            yymmdd_log = dt.strftime('%y%m%d')
            #小於昨天繼續
            if yymmdd_log<yymmdd_yestoday:
                #print('跳過,小於 %s'%yymmdd_yestoday)
                continue
            #大於昨天退出
            if yymmdd_log>yymmdd_yestoday:
                print('退出,大於 %s'%yymmdd_yestoday)
                break
            #print(line)
            outputFile.writelines("%s"%line)

    #關閉輸出文件流
    if outputFile:
        outputFile.close()
    #分離後刪除 tmp 文件
    if os.path.exists(bacFileFullName):
        os.remove(tmpFileFullName)
        print('刪除臨時文件,%s\t%s'%(tmpFileFullName
                               ,not os.path.exists(tmpFileFullName)))
    print('\n\n%s\n拆分完成,耗時 %s 秒 \nlog=%s' % ('*' * 30
                                            , (datetime.datetime.now() - start).seconds
                                            , bacFileFullName))
    pass
'''
>>> f = open('test.txt', 'w') # 若是'wb'就表示寫二進制文件
>>> f.write('Hello, world!')
>>> f.close()
python文件對象提供了兩個“寫”方法: write() 和 writelines()。
write()方法和read()、readline()方法對應,是將字符串寫入到文件中。
writelines()方法和readlines()方法對應,也是針對列表的操作。它接收一個字符串列表作爲參數,將他們寫入到文件中,換行符不會自動的加入,因此,需要顯式的加入換行符。
關於open()的mode參數:
'r':讀
'w':寫
'a':追加
'r+' == r+w(可讀可寫,文件若不存在就報錯(IOError))
'w+' == w+r(可讀可寫,文件若不存在就創建)
'a+' ==a+r(可追加可寫,文件若不存在就創建)
對應的,如果是二進制文件,就都加一個b就好啦:
'rb'  'wb'  'ab'  'rb+'  'wb+'  'ab+'
'''

def test():
    # "24/Apr/2020:14:43:38 +0800"
    dt =time.time()
    print(time.strftime('%Y-%m-%d %H:%M:%S [%Z]',time.localtime(dt)))
    print(time.strftime('%y-%m-%d %I:%M:%S [%Z]',time.localtime(dt)))
    print(time.strftime('%d/%b/%Y %H:%M:%S [%Z]',time.localtime(dt)))
    print('-'*30)
    str = '24/Apr/2020:14:43:38'
    dt = datetime.datetime.strptime(str,'%d/%b/%Y:%H:%M:%S')
    print("%s[%s] => %s[%s]" % (str,type(str),dt,type(dt)))
    str = dt.strftime('%Y-%m-%d %H:%M:%S')
    print("%s [%s]" % (str,type(str)))
    pass

'''
python中時間日期格式化符號:
    %y 兩位數的年份表示(00-99)
    %Y 四位數的年份表示(000-9999)
    %m 月份(01-12)
    %d 月內中的一天(0-31)
    %H 24小時制小時數(0-23)
    %I 12小時制小時數(01-12) 
    %M 分鐘數(00=59)
    %S 秒(00-59)    
    %a 本地簡化星期名稱
    %A 本地完整星期名稱
    %b 本地簡化的月份名稱
    %B 本地完整的月份名稱
    %c 本地相應的日期表示和時間表示
    %j 年內的一天(001-366)
    %p 本地A.M.或P.M.的等價符
    %U 一年中的星期數(00-53)星期天爲星期的開始
    %w 星期(0-6),星期天爲星期的開始
    %W 一年中的星期數(00-53)星期一爲星期的開始
    %x 本地相應的日期表示
    %X 本地相應的時間表示
    %Z 當前時區的名稱   
'''
def createTempFile(nginxConf,nginxDir,prefixArr):
    yestoday = datetime.date.today()-datetime.timedelta(days=1)
    yymmdd = yestoday.strftime('%y%m%d')
    for prefix in prefixArr:
        logFileFullName = os.path.join(nginxDir,"logs","%s.log"%prefix)
        tmpFileullName = os.path.join(nginxDir,"logs","%s_%s.log"%(prefix,yymmdd))
        if not os.path.exists(logFileFullName):
            print('log 文件不已存在:%s'%tmpFileullName)
            continue
        if os.path.exists(tmpFileullName):
            print('tmp 文件已存在:%s'%tmpFileullName)
            continue
        #備份log
        os.rename(logFileFullName,tmpFileullName)
        if not os.path.exists(tmpFileullName):
            print('log 重命名失敗:%s'%logFileFullName)
            continue
        print('%s rename %s'%(tmpFileullName,os.path.exists(tmpFileullName)))

    #重啓 nginx
    cmd = 'nginx -p %s -c %s -s reload'%(nginxDir,nginxConf)
    print('%s\n執行 nginx reload 命令\n\t%s\n\n'%('-'*60,cmd))
    #os.system() 將導致進程阻塞
    os.system(cmd)
    #等待重啓
    time.sleep(3)
    #判斷文件是否存在
    print('rolad 命令已觸發,驗證log 是否新建')
    for prefix in prefixArr:
        log = os.path.join(nginxDir,"logs",'%s.log'%prefix)
        print('\t%s rename %s'%(log,os.path.exists(log)))
    print('\n')

def main(nginxConf,nginxDir, logPrefixs):
    if not nginxDir or not logPrefixs:
        print("參數爲空:--nginxDir={} --logPrefixs={}".format(nginxDir, logPrefixs))
        return
    if not os.path.exists(nginxDir):
        print("文件不存在:--nginxDir={} ".format(nginxDir))
        return
    conf = os.path.join(nginxDir,nginxConf)
    if not os.path.exists(conf):
        print("nginx config 不存在:--nginxConf={} ".format(conf))
        return
    prefixArr = logPrefixs.split(',')
    #備份+重新加載 nginx
    createTempFile(nginxConf,nginxDir,prefixArr)

    #分離當天的log
    for prefix in prefixArr:
        try:
            print("備份 %s 文件"%prefix)
            logSpliter(nginxDir, prefix)
        except Exception as ex:
            print("備份 %s 異常"%prefix,ex)
    pass


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='manual to this script')
    parser.add_argument('--nginxConf', type=str, default = None)
    parser.add_argument('--nginxDir', type=str, default = None)
    parser.add_argument('--logPrefixs', type=str, default= None)
    args = parser.parse_args()
    #test()
    '''    
    功能:
       備份執行時間-1天(昨天)的 nginx log,需要指定 log 的前綴,多個文件名逗號分隔;
    運行邏輯:
        1.將指定前綴的 log 在同目錄創建一個臨時文件(對源文件重命名),如:access_200426.log;
        2.使用 nginx -s 命令,從容重啓 nginx,重新創建 log;
        3.讀 access_200426.log 文件,將記是 2020-04-26 產生的日誌,轉存至 ./bac/access_200426.log 文件中;
        4.刪除臨時文件 access_200426.log ;
        注:同一天可多次執行,轉存的 log 將增量添加;
    調用方式:
        python nginx_logs_splter.py --nginxConf=nginx.conf --nginxDir=xxxxx --logPrefixs=access,error
        參數:
            nginxConf=nginx 配置文件
            nginxDir=nginx 目錄
            logPrefixs=log文件前綴(多個逗號分隔)
    windows 部署:
        1.在 nginx 目錄,創建一個 nginx_logs_backup.bat 文件;文件內容如下
            python nginx_logs_splter.py --nginxConf=nginx.conf --nginxDir=xxxxx --logPrefixs=access,error
        2.在定時任務中加一個定時任務,調用這個 bat 文件;
            2.1 開始-程序-管理工具-任務計劃程序;
            2.2 新建基本任務;
            2.3 注意的一點是,在"編輯操作"窗口,在"起始於(可選)"這一欄需要填入 bat 所在目錄,否則 bat 不會執行;
    '''
    sys.exit(main(args.nginxConf,args.nginxDir,args.logPrefixs))

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章