解析Apache日志文件

37
我刚开始学习Python,想读取Apache日志文件并将每行的部分放入不同的列表中。
文件中的一行内容如下:
172.16.0.3 - - [25/Sep/2002:14:04:19 +0200] "GET / HTTP/1.1" 401 - "" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1) Gecko/20020827"
根据Apache网站上的说明,格式如下:
%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"
我可以打开文件并按原样读取,但我不知道如何使其按照这种格式进行读取,以便将每个部分放入列表中。

你对这行代码的哪些元素感兴趣?(全部吗?) - David Robinson
由于代码行略有变化,我希望它们以完全相同的格式读取。 - ogward
你误解了- 我的意思是,你想从每一行中提取什么?日期?IP地址?还是全部都要? - David Robinson
你想改变Apache日志文件存储的结构吗? - Surya
@DavidRobinson 我想把IP放在一个列表中,第一个“-”没有意义,第二个“-”是用户,所以应该放在用户列表中,日期和时间放在另一个列表中等等。 - ogward
@Surya 不,只需要能够阅读 Apache 文件中每行的每个部分即可。 - ogward
6个回答

47
这需要使用正则表达式技术。
例如:
line = '172.16.0.3 - - [25/Sep/2002:14:04:19 +0200] "GET / HTTP/1.1" 401 - "" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1) Gecko/20020827"'
regex = '([(\d\.)]+) - - \[(.*?)\] "(.*?)" (\d+) - "(.*?)" "(.*?)"'

import re
print re.match(regex, line).groups()

输出将是一个元组,其中包含来自该行中括号内的6个信息组(即该模式中的分组):

('172.16.0.3', '25/Sep/2002:14:04:19 +0200', 'GET / HTTP/1.1', '401', '', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1) Gecko/20020827')

这看起来不错,但我能不能为每个部分都有一个“正则表达式”呢? 像这样 ´ip = '([(\d.)]+)' date = '.........' req = '............'´ - ogward
我想我弄明白了。非常感谢! - ogward
1
当我尝试从文件中选择其他行时,它无法工作。 例如:127.0.0.1 - stefan [01/Apr/2002:12:17:21 +0200] "GET /sit3-shine.7.gif HTTP/1.1" 200 15811 "http://localhost/" "Mozilla/5.0(compatible; Konqueror/2.2.2-2; Linux)" - ogward
3
@ogward 说它没有起作用的一个原因是第二个破折号不总是破折号-在这种情况下,它是名字stefan。在模式中用(.*)替换它。(文件中可能还有其他行存在问题-我建议你熟悉正则表达式,相信你能够适应我的示例。) - David Robinson
3
实际上,这并不是正则表达式的工作,因为请求中任何用户提供的行都可能包含空格或引号,从而影响解析。例如,如果有人使用未进行 URL 转义的客户端连接。谁会这样做呢?黑客!很容易看出这会导致什么问题,只需使用 telnet 或 netcat 连接到 80 端口并进行手动请求,如下所示: GET /dlkfj" lsakjdf HTTP/1.1 - deltaray

14

我创建了一个Python库,它可以做到这一点:apache-log-parser

>>> import apache_log_parser
 >>> line_parser = apache_log_parser.make_parser("%h <<%P>> %t %Dus \"%r\" %>s %b  \"%{Referer}i\" \"%{User-Agent}i\" %l %u")
>>> log_line_data = line_parser('127.0.0.1 <<6113>> [16/Aug/2013:15:45:34 +0000] 1966093us "GET / HTTP/1.1" 200 3478  "https://example.com/" "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.18)" - -')
>>> pprint(log_line_data)
{'pid': '6113',
 'remote_host': '127.0.0.1',
 'remote_logname': '-',
 'remote_user': '',
 'request_first_line': 'GET / HTTP/1.1',
 'request_header_referer': 'https://example.com/',
 'request_header_user_agent': 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.18)',
 'response_bytes_clf': '3478',
 'status': '200',
 'time_received': '[16/Aug/2013:15:45:34 +0000]',
 'time_us': '1966093'}

13
使用正则表达式将一行文本拆分为单独的“标记”:
>>> row = """172.16.0.3 - - [25/Sep/2002:14:04:19 +0200] "GET / HTTP/1.1" 401 - "" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1) Gecko/20020827" """
>>> import re
>>> map(''.join, re.findall(r'\"(.*?)\"|\[(.*?)\]|(\S+)', row))
['172.16.0.3', '-', '-', '25/Sep/2002:14:04:19 +0200', 'GET / HTTP/1.1', '401', '-', '', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1) Gecko/20020827']

另一种解决方案是使用专门的工具,例如http://pypi.python.org/pypi/pylogsparser/0.4


9

考虑到格式的简单性,正则表达式似乎过于复杂和有问题,因此我编写了这个小分割器,其他人也可能会发现它很有用:

def apache2_logrow(s):
    ''' Fast split on Apache2 log lines

    http://httpd.apache.org/docs/trunk/logs.html
    '''
    row = [ ]
    qe = qp = None # quote end character (qe) and quote parts (qp)
    for s in s.replace('\r','').replace('\n','').split(' '):
        if qp:
            qp.append(s)
        elif '' == s: # blanks
            row.append('')
        elif '"' == s[0]: # begin " quote "
            qp = [ s ]
            qe = '"'
        elif '[' == s[0]: # begin [ quote ]
            qp = [ s ]
            qe = ']'
        else:
            row.append(s)

        l = len(s)
        if l and qe == s[-1]: # end quote
            if l == 1 or s[-2] != '\\': # don't end on escaped quotes
                row.append(' '.join(qp)[1:-1].replace('\\'+qe, qe))
                qp = qe = None
    return row

这个方法对我在Python 3中起作用。其他正则表达式示例会出现错误。谢谢。 - logic-unit

5
import re


HOST = r'^(?P<host>.*?)'
SPACE = r'\s'
IDENTITY = r'\S+'
USER = r'\S+'
TIME = r'(?P<time>\[.*?\])'
REQUEST = r'\"(?P<request>.*?)\"'
STATUS = r'(?P<status>\d{3})'
SIZE = r'(?P<size>\S+)'

REGEX = HOST+SPACE+IDENTITY+SPACE+USER+SPACE+TIME+SPACE+REQUEST+SPACE+STATUS+SPACE+SIZE+SPACE

def parser(log_line):
    match = re.search(REGEX,log_line)
    return ( (match.group('host'),
            match.group('time'), 
                      match.group('request') , 
                      match.group('status') ,
                      match.group('size')
                     )
                   )


logLine = """180.76.15.30 - - [24/Mar/2017:19:37:57 +0000] "GET /shop/page/32/?count=15&orderby=title&add_to_wishlist=4846 HTTP/1.1" 404 10202 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"""
result = parser(logLine)
print(result)

2
在httpd.conf中添加以下内容,将Apache日志转换为JSON格式。
LogFormat "{\"time\":\"%t\", \"remoteIP\" :\"%a\", \"host\": \"%V\", \"request_id\": \"%L\", \"request\":\"%U\", \"query\" : \"%q\", \"method\":\"%m\", \"status\":\"%>s\", \"userAgent\":\"%{User-agent}i\", \"referer\":\"%{Referer}i\" }" json_log

CustomLog /var/log/apache_access_log json_log
CustomLog "|/usr/bin/python -u apacheLogHandler.py" json_log

现在您可以以JSON格式查看access_logs。使用以下Python代码解析不断更新的JSON日志。

apacheLogHandler.py

import time
f = open('apache_access_log.log', 'r')
for line in f: # read all lines already in the file
  print line.strip()

# keep waiting forever for more lines.
while True:
  line = f.readline() # just read more
  if line: # if you got something...
    print 'got data:', line.strip()
  time.sleep(1)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接