syslog 로그 파일에서 시간 범위를 빠르게 추출 하시겠습니까?


12

표준 syslog 형식의 로그 파일이 있습니다. 초당 수백 줄을 제외하고는 다음과 같습니다.

Jan 11 07:48:46 blahblahblah...
Jan 11 07:49:00 blahblahblah...
Jan 11 07:50:13 blahblahblah...
Jan 11 07:51:22 blahblahblah...
Jan 11 07:58:04 blahblahblah...

정확히 자정에 굴러 가지는 않지만 이틀이 넘지 않습니다.

이 파일에서 종종 타임 슬라이스를 추출해야합니다. 다음과 같이 호출 할 수있는 범용 스크립트를 작성하고 싶습니다.

$ timegrep 22:30-02:00 /logs/something.log

... 오후 22시 30 분부터 자정 경계를지나 다음 날 오전 2 시까 지 줄을 뽑습니다.

몇 가지주의 사항이 있습니다.

  • 커맨드 라인에 날짜 만 입력하는 것을 귀찮게하고 싶지는 않습니다. 프로그램은 그것들을 알아낼 수있을 정도로 똑똑해야합니다.
  • 로그 날짜 형식은 연도를 포함하지 않으므로 현재 연도를 기준으로 추측해야하지만 그럼에도 불구하고 새해 첫날에 올바른 일을합니다.
  • 나는 그것이 빠르기를 원합니다. 파일에서 탐색하고 이진 검색을 사용하기 위해 행이 있다는 사실을 사용해야합니다.

이것을 쓰는 데 많은 시간을 보내기 전에 이미 존재합니까?

답변:


9

업데이트 : 원래 코드를 여러 가지 개선 사항으로 업데이트 된 버전으로 교체했습니다. 이것을 (실제?) 알파 품질이라고합시다.

이 버전에는 다음이 포함됩니다.

  • 명령 줄 옵션 처리
  • 명령 줄 날짜 형식 유효성 검사
  • 일부 try블록
  • 라인 판독 기능으로 이동

원문 :

당신은 무엇을 알고 있습니까? "찾아라"그러면 찾을 것이다! 다음은 파일에서 탐색하고 다소 이진 검색을 사용하는 Python 프로그램입니다. 그건 상당히 빨리 그 AWK 스크립트 이외 의 다른 사람이 있음을 썼다.

알파 품질입니다. 그것은이 있어야 try블록 및 입력 검증 및 테스트를 많이하고 의심의 여지가 더 파이썬 수 없었다. 그러나 여기 당신의 즐거움입니다. 아, 그리고 그것은 파이썬 2.6을 위해 작성되었습니다.

새로운 코드 :

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# timegrep.py by Dennis Williamson 20100113
# in response to http://serverfault.com/questions/101744/fast-extraction-of-a-time-range-from-syslog-logfile

# thanks to serverfault user http://serverfault.com/users/1545/mike
# for the inspiration

# Perform a binary search through a log file to find a range of times
# and print the corresponding lines

# tested with Python 2.6

# TODO: Make sure that it works if the seek falls in the middle of
#       the first or last line
# TODO: Make sure it's not blind to a line where the sync read falls
#       exactly at the beginning of the line being searched for and
#       then gets skipped by the second read
# TODO: accept arbitrary date

# done: add -l long and -s short options
# done: test time format

version = "0.01a"

import os, sys
from stat import *
from datetime import date, datetime
import re
from optparse import OptionParser

# Function to read lines from file and extract the date and time
def getdata():
    """Read a line from a file

    Return a tuple containing:
        the date/time in a format such as 'Jan 15 20:14:01'
        the line itself

    The last colon and seconds are optional and
    not handled specially

    """
    try:
        line = handle.readline(bufsize)
    except:
        print("File I/O Error")
        exit(1)
    if line == '':
        print("EOF reached")
        exit(1)
    if line[-1] == '\n':
        line = line.rstrip('\n')
    else:
        if len(line) >= bufsize:
            print("Line length exceeds buffer size")
        else:
            print("Missing newline")
        exit(1)
    words = line.split(' ')
    if len(words) >= 3:
        linedate = words[0] + " " + words[1] + " " + words[2]
    else:
        linedate = ''
    return (linedate, line)
# End function getdata()

# Set up option handling
parser = OptionParser(version = "%prog " + version)

parser.usage = "\n\t%prog [options] start-time end-time filename\n\n\
\twhere times are in the form hh:mm[:ss]"

parser.description = "Search a log file for a range of times occurring yesterday \
and/or today using the current time to intelligently select the start and end. \
A date may be specified instead. Seconds are optional in time arguments."

parser.add_option("-d", "--date", action = "store", dest = "date",
                default = "",
                help = "NOT YET IMPLEMENTED. Use the supplied date instead of today.")

parser.add_option("-l", "--long", action = "store_true", dest = "longout",
                default = False,
                help = "Span the longest possible time range.")

parser.add_option("-s", "--short", action = "store_true", dest = "shortout",
                default = False,
                help = "Span the shortest possible time range.")

parser.add_option("-D", "--debug", action = "store", dest = "debug",
                default = 0, type = "int",
                help = "Output debugging information.\t\t\t\t\tNone (default) = %default, Some = 1, More = 2")

(options, args) = parser.parse_args()

if not 0 <= options.debug <= 2:
    parser.error("debug level out of range")
else:
    debug = options.debug    # 1 = print some debug output, 2 = print a little more, 0 = none

if options.longout and options.shortout:
    parser.error("options -l and -s are mutually exclusive")

if options.date:
    parser.error("date option not yet implemented")

if len(args) != 3:
    parser.error("invalid number of arguments")

start = args[0]
end   = args[1]
file  = args[2]

# test for times to be properly formatted, allow hh:mm or hh:mm:ss
p = re.compile(r'(^[2][0-3]|[0-1][0-9]):[0-5][0-9](:[0-5][0-9])?$')

if not p.match(start) or not p.match(end):
    print("Invalid time specification")
    exit(1)

# Determine Time Range
yesterday = date.fromordinal(date.today().toordinal()-1).strftime("%b %d")
today     = datetime.now().strftime("%b %d")
now       = datetime.now().strftime("%R")

if start > now or start > end or options.longout or options.shortout:
    searchstart = yesterday
else:
    searchstart = today

if (end > start > now and not options.longout) or options.shortout:
    searchend = yesterday
else:
    searchend = today

searchstart = searchstart + " " + start
searchend = searchend + " " + end

try:
    handle = open(file,'r')
except:
    print("File Open Error")
    exit(1)

# Set some initial values
bufsize = 4096  # handle long lines, but put a limit them
rewind  =  100  # arbitrary, the optimal value is highly dependent on the structure of the file
limit   =   75  # arbitrary, allow for a VERY large file, but stop it if it runs away
count   =    0
size    =    os.stat(file)[ST_SIZE]
beginrange   = 0
midrange     = size / 2
oldmidrange  = midrange
endrange     = size
linedate     = ''

pos1 = pos2  = 0

if debug > 0: print("File: '{0}' Size: {1} Today: '{2}' Now: {3} Start: '{4}' End: '{5}'".format(file, size, today, now, searchstart, searchend))

# Seek using binary search
while pos1 != endrange and oldmidrange != 0 and linedate != searchstart:
    handle.seek(midrange)
    linedate, line = getdata()    # sync to line ending
    pos1 = handle.tell()
    if midrange > 0:             # if not BOF, discard first read
        if debug > 1: print("...partial: (len: {0}) '{1}'".format((len(line)), line))
        linedate, line = getdata()

    pos2 = handle.tell()
    count += 1
    if debug > 0: print("#{0} Beg: {1} Mid: {2} End: {3} P1: {4} P2: {5} Timestamp: '{6}'".format(count, beginrange, midrange, endrange, pos1, pos2, linedate))
    if  searchstart > linedate:
        beginrange = midrange
    else:
        endrange = midrange
    oldmidrange = midrange
    midrange = (beginrange + endrange) / 2
    if count > limit:
        print("ERROR: ITERATION LIMIT EXCEEDED")
        exit(1)

if debug > 0: print("...stopping: '{0}'".format(line))

# Rewind a bit to make sure we didn't miss any
seek = oldmidrange
while linedate >= searchstart and seek > 0:
    if seek < rewind:
        seek = 0
    else:
        seek = seek - rewind
    if debug > 0: print("...rewinding")
    handle.seek(seek)

    linedate, line = getdata()    # sync to line ending
    if debug > 1: print("...junk: '{0}'".format(line))

    linedate, line = getdata()
    if debug > 0: print("...comparing: '{0}'".format(linedate))

# Scan forward
while linedate < searchstart:
    if debug > 0: print("...skipping: '{0}'".format(linedate))
    linedate, line = getdata()

if debug > 0: print("...found: '{0}'".format(line))

if debug > 0: print("Beg: {0} Mid: {1} End: {2} P1: {3} P2: {4} Timestamp: '{5}'".format(beginrange, midrange, endrange, pos1, pos2, linedate))

# Now that the preliminaries are out of the way, we just loop,
#     reading lines and printing them until they are
#     beyond the end of the range we want

while linedate <= searchend:
    print line
    linedate, line = getdata()

if debug > 0: print("Start: '{0}' End: '{1}'".format(searchstart, searchend))
handle.close()

와. 나는 정말로 파이썬을 배워야한다 ...
— Stefan Lasiewski

@Dennis Williamson :가 포함 된 줄이 보입니다 if debug > 0: print("File: '{0}' Size: {1} Today: '{2}' Now: {3} Start: '{4}' End: '{5}'".format(file, size, today, now, searchstar$. 은 searchstar로 끝날 가정 $, 또는 오타이다? 이 줄에 구문 오류가 발생합니다 (159 행)
— Stefan Lasiewski

@Stefan 나는 그것을로 대체 할 것입니다 )).
— Bill Weiss

@ 스테판 : 감사합니다. 내가 고친 오타였습니다. 빠른 참조를 들어, $대신해야 t, searchend))그것이 말하는 때문에... searchstart, searchend))
— 추후 공지가있을 때까지 일시 중지.

@ 스테판 : 죄송합니다. 나는 그것이 있다고 생각합니다.
— 추후 공지가있을 때까지 일시 중지되었습니다.

0

인터넷에서 빠른 검색을 수행하면 키워드를 기반으로 추출하는 항목 (예 : FIRE 등)이 있지만 파일에서 날짜 범위를 추출하는 것은 없습니다.

제안한 것을하는 것이 어렵지 않은 것 같습니다.

  1. 시작 시간을 검색하십시오.
  2. 그 줄을 인쇄하십시오.
  3. 종료 시간 <시작 시간이고 행 날짜가> 종료 및 <시작 인 경우 중지하십시오.
  4. 종료 시간이> 시작 시간이고 라인 날짜가> 종료 인 경우 중지하십시오.

똑바로 보입니다. 루비가 마음에 들지 않으면 쓸 수 있습니다. :)


루비는 신경 쓰지 않지만 큰 파일에서 효율적으로 작업하려면 # 1이 간단하지 않습니다. 반쯤 지점으로 seek ()를 시도하고 가장 가까운 줄을 찾고 시작 방법을 확인하고 반복해야합니다. 새로운 중간 점. 모든 라인을보기에는 너무 비효율적입니다.
— Mike

크게 말했지만 실제 크기를 지정하지 않았습니다. 얼마나 큰가요? 더 나쁜 것은, 여러 날이 관련되어 있다면, 시간을 사용해서 만 잘못된 것을 찾는 것이 매우 쉽다는 것입니다. 결국 하루 경계를 넘으면 스크립트가 실행되는 날짜는 항상 시작 시간과 다릅니다. 파일이 mmap ()을 통해 메모리에 맞습니까?
— Michael Graff

네트워크 마운트 디스크에서 약 30GB
— mike

0

현재 시간과의 관련성 ( "현재")에 따라 시작 시간과 종료 시간 사이의 항목 범위를 인쇄합니다.

용법:

timegrep [-l] start end filename

예:

$ timegrep 18:47 03:22 /some/log/file

-l(긴) 옵션은 가능한 가장 긴 출력됩니다. 시작 시간의시 및 분 값이 종료 시간과 현재 시간보다 작은 경우 시작 시간은 어제로 해석됩니다. 시작 시간과 종료 시간 HH : MM 값이 모두 "지금"보다 큰 경우 종료 시간은 오늘로 해석됩니다.

"현재"가 "Jan 11 19:00"이라고 가정하면 다음은 시작 및 종료 시간의 다양한 예를 해석하는 방법입니다 ( -l표시된 것을 제외하고).

시작 끝 범위 시작 범위 끝
19:01 23:59 1 월 10 일 1 월 10 일
19:01 00:00 1 월 10 일 1 월 11 일
00:00 18:59 1 월 11 일 1 월 11 일
18:59 18:58 1 월 10 일 1 월 10 일
19:01 23:59 1 월 10 일 1 월 11 일 # -l
00:00 18:59 1 월 10 일 1 월 11 일 # -l
18:59 19:01 1 월 10 일 1 월 11 일 # -l

거의 모든 스크립트가 설정되었습니다. 마지막 두 줄은 모든 작업을 수행합니다.

경고 : 인수 유효성 검사 또는 오류 검사가 수행되지 않습니다. 엣지 케이스는 철저히 테스트되지 않았습니다. 이것은 gawk다른 버전의 AWK를 사용하여 작성되었습니다 .

#!/usr/bin/awk -f
BEGIN {
    arg=1
    if ( ARGV[arg] == "-l" ) {
        long = 1
        ARGV[arg++] = ""
    }
    start = ARGV[arg]
    ARGV[arg++] = ""
    end = ARGV[arg]
    ARGV[arg++] = ""

    yesterday = strftime("%b %d", mktime(strftime("%Y %m %d -24 00 00")))
    today = strftime("%b %d")
    now = strftime("%R")

    if ( start > now || start > end || long )
        startdate = yesterday
    else
        startdate = today

    if ( end > now && end > start && start > now && ! long )
        enddate = yesterday
    else
        enddate = today
    fi

startdate = startdate " " start
enddate = enddate " " end
}

$1 " " $2 " " $3 > enddate {exit}
$1 " " $2 " " $3 >= startdate {print}

AWK가 파일을 검색하는 데 매우 효율적이라고 생각합니다. 색인화되지 않은 텍스트 파일 을 검색 할 때 다른 것이 반드시 더 빠를 것이라고 생각하지 않습니다 .


내 세 번째 글 머리 기호를 간과 한 것 같습니다. 로그의 순서는 30GB입니다. 파일의 첫 번째 줄이 7:00이고 마지막 줄이 23:00이고 22:00에서 22:01 사이의 슬라이스를 원하면 원하지 않습니다. 스크립트는 7:00에서 22:00 사이의 모든 줄을 봅니다. 나는 그것이 어디에 있을지 추정하고, 그 시점을 찾고, 그것을 찾을 때까지 새로운 추정을하기를 원합니다.
— mike

나는 그것을 간과하지 않았다. 나는 마지막 단락에 내 의견을 표명했다.
— 추후 공지가있을 때까지 일시 중지되었습니다.

0

이진 검색을 적용하는 C ++ 프로그램-텍스트 날짜를 처리하려면 간단한 수정 (예 : strptime 호출)이 필요합니다.

http://gitorious.org/bs_grep/

텍스트 날짜를 지원하는 이전 버전이 있었지만 여전히 로그 파일의 규모에 비해 너무 느 렸습니다. 프로파일 링에 따르면 시간의 90 % 이상이 strptime에 소비되었다고 말하면서 숫자 unix 타임 스탬프도 포함하도록 로그 형식을 수정했습니다.


0

이 답변이 너무 늦었지만 일부에게는 도움이 될 수 있습니다.

@Dennis Williamson의 코드를 다른 파이썬 물건에 사용할 수있는 Python 클래스로 변환했습니다.

여러 날짜 지원에 대한 지원을 추가했습니다.

import os
from stat import *
from datetime import date, datetime
import re

# @TODO Support for rotated log files - currently using the current year for 'Jan 01' dates.
class LogFileTimeParser(object):
    """
    Extracts parts of a log file based on a start and enddate
    Uses binary search logic to speed up searching

    Common usage: validate log files during testing

    Faster than awk parsing for big log files
    """
    version = "0.01a"

    # Set some initial values
    BUF_SIZE = 4096  # self.handle long lines, but put a limit to them
    REWIND = 100  # arbitrary, the optimal value is highly dependent on the structure of the file
    LIMIT = 75  # arbitrary, allow for a VERY large file, but stop it if it runs away

    line_date = ''
    line = None
    opened_file = None

    @staticmethod
    def parse_date(text, validate=True):
        # Supports Aug 16 14:59:01 , 2016-08-16 09:23:09 Jun 1 2005  1:33:06PM (with or without seconds, miliseconds)
        for fmt in ('%Y-%m-%d %H:%M:%S %f', '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
                    '%b %d %H:%M:%S %f', '%b %d %H:%M', '%b %d %H:%M:%S',
                    '%b %d %Y %H:%M:%S %f', '%b %d %Y %H:%M', '%b %d %Y %H:%M:%S',
                    '%b %d %Y %I:%M:%S%p', '%b %d %Y %I:%M%p', '%b %d %Y %I:%M:%S%p %f'):
            try:
                if fmt in ['%b %d %H:%M:%S %f', '%b %d %H:%M', '%b %d %H:%M:%S']:

                    return datetime.strptime(text, fmt).replace(datetime.now().year)
                return datetime.strptime(text, fmt)
            except ValueError:
                pass
        if validate:
            raise ValueError("No valid date format found for '{0}'".format(text))
        else:
            # Cannot use NoneType to compare datetimes. Using minimum instead
            return datetime.min

    # Function to read lines from file and extract the date and time
    def read_lines(self):
        """
        Read a line from a file
        Return a tuple containing:
            the date/time in a format supported in parse_date om the line itself
        """
        try:
            self.line = self.opened_file.readline(self.BUF_SIZE)
        except:
            raise IOError("File I/O Error")
        if self.line == '':
            raise EOFError("EOF reached")
        # Remove \n from read lines.
        if self.line[-1] == '\n':
            self.line = self.line.rstrip('\n')
        else:
            if len(self.line) >= self.BUF_SIZE:
                raise ValueError("Line length exceeds buffer size")
            else:
                raise ValueError("Missing newline")
        words = self.line.split(' ')
        # This results into Jan 1 01:01:01 000000 or 1970-01-01 01:01:01 000000
        if len(words) >= 3:
            self.line_date = self.parse_date(words[0] + " " + words[1] + " " + words[2],False)
        else:
            self.line_date = self.parse_date('', False)
        return self.line_date, self.line

    def get_lines_between_timestamps(self, start, end, path_to_file, debug=False):
        # Set some initial values
        count = 0
        size = os.stat(path_to_file)[ST_SIZE]
        begin_range = 0
        mid_range = size / 2
        old_mid_range = mid_range
        end_range = size
        pos1 = pos2 = 0

        # If only hours are supplied
        # test for times to be properly formatted, allow hh:mm or hh:mm:ss
        p = re.compile(r'(^[2][0-3]|[0-1][0-9]):[0-5][0-9](:[0-5][0-9])?$')
        if p.match(start) or p.match(end):
            # Determine Time Range
            yesterday = date.fromordinal(date.today().toordinal() - 1).strftime("%Y-%m-%d")
            today = datetime.now().strftime("%Y-%m-%d")
            now = datetime.now().strftime("%R")
            if start > now or start > end:
                search_start = yesterday
            else:
                search_start = today
            if end > start > now:
                search_end = yesterday
            else:
                search_end = today
            search_start = self.parse_date(search_start + " " + start)
            search_end = self.parse_date(search_end + " " + end)
        else:
            # Set dates
            search_start = self.parse_date(start)
            search_end = self.parse_date(end)
        try:
            self.opened_file = open(path_to_file, 'r')
        except:
            raise IOError("File Open Error")
        if debug:
            print("File: '{0}' Size: {1} Start: '{2}' End: '{3}'"
                  .format(path_to_file, size, search_start, search_end))

        # Seek using binary search -- ONLY WORKS ON FILES WHO ARE SORTED BY DATES (should be true for log files)
        try:
            while pos1 != end_range and old_mid_range != 0 and self.line_date != search_start:
                self.opened_file.seek(mid_range)
                # sync to self.line ending
                self.line_date, self.line = self.read_lines()
                pos1 = self.opened_file.tell()
                # if not beginning of file, discard first read
                if mid_range > 0:
                    if debug:
                        print("...partial: (len: {0}) '{1}'".format((len(self.line)), self.line))
                    self.line_date, self.line = self.read_lines()
                pos2 = self.opened_file.tell()
                count += 1
                if debug:
                    print("#{0} Beginning: {1} Mid: {2} End: {3} P1: {4} P2: {5} Timestamp: '{6}'".
                          format(count, begin_range, mid_range, end_range, pos1, pos2, self.line_date))
                if search_start > self.line_date:
                    begin_range = mid_range
                else:
                    end_range = mid_range
                old_mid_range = mid_range
                mid_range = (begin_range + end_range) / 2
                if count > self.LIMIT:
                    raise IndexError("ERROR: ITERATION LIMIT EXCEEDED")
            if debug:
                print("...stopping: '{0}'".format(self.line))
            # Rewind a bit to make sure we didn't miss any
            seek = old_mid_range
            while self.line_date >= search_start and seek > 0:
                if seek < self.REWIND:
                    seek = 0
                else:
                    seek -= self.REWIND
                if debug:
                    print("...rewinding")
                self.opened_file.seek(seek)
                # sync to self.line ending
                self.line_date, self.line = self.read_lines()
                if debug:
                    print("...junk: '{0}'".format(self.line))
                self.line_date, self.line = self.read_lines()
                if debug:
                    print("...comparing: '{0}'".format(self.line_date))
            # Scan forward
            while self.line_date < search_start:
                if debug:
                    print("...skipping: '{0}'".format(self.line_date))
                self.line_date, self.line = self.read_lines()
            if debug:
                print("...found: '{0}'".format(self.line))
            if debug:
                print("Beginning: {0} Mid: {1} End: {2} P1: {3} P2: {4} Timestamp: '{5}'".
                      format(begin_range, mid_range, end_range, pos1, pos2, self.line_date))
            # Now that the preliminaries are out of the way, we just loop,
            # reading lines and printing them until they are beyond the end of the range we want
            while self.line_date <= search_end:
                # Exclude our 'Nonetype' values
                if not self.line_date == datetime.min:
                    print self.line
                self.line_date, self.line = self.read_lines()
            if debug:
                print("Start: '{0}' End: '{1}'".format(search_start, search_end))
            self.opened_file.close()
        # Do not display EOFErrors:
        except EOFError as e:
            pass
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.