Выделяем поля из логов nginx на python (original) (raw)

January 1 2010, 18:30

Для анализа логов nginx мне понадобилось регулярное выражение, которое бы корректно разделяло запись на отдельные поля. Немного по-колдовав над логом, я понял, что наилучший способ разобраться в формате лога - это выцепить его из конфигурации сервера.

Кроме очевидного плюса - выделение полей с учетом всех разделителей и кавычек, использование формата позволяет получить именованные поля.

Вот код, который у меня получился, возможно пригодится.

import re

# формат из конфигурации nginx
FORMAT = '$remote_addr - <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>r</mi><mi>e</mi><mi>m</mi><mi>o</mi><mi>t</mi><msub><mi>e</mi><mi>u</mi></msub><mi>s</mi><mi>e</mi><mi>r</mi><mo stretchy="false">[</mo></mrow><annotation encoding="application/x-tex">remote_user [</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">re</span><span class="mord mathnormal">m</span><span class="mord mathnormal">o</span><span class="mord mathnormal">t</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">u</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mopen">[</span></span></span></span>time_local] "$request" $status' + \
    ' <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi><mi>y</mi><mi>t</mi><mi>e</mi><msub><mi>s</mi><mi>s</mi></msub><mi>e</mi><mi>n</mi><mi>t</mi><mi mathvariant="normal">&quot;</mi></mrow><annotation encoding="application/x-tex">bytes_sent &quot;</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">b</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">s</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span><span class="mord">&quot;</span></span></span></span>http_referer"' + \
    ' "$http_user_agent" "$gzip_ratio" "$cookie" $hostname'

# выражение, описывающее переменную в конфигурации логов nginx
TOKEN = '\$([a-z\_]+)'

# возможные скобки и кавычки вокруг переменных в конфигурации логов nginx
POSSIBLE_QUOTES = ('[]', '"', '\'')

def get_matcher(format):
    quotes = re.escape(''.join(POSSIBLE_QUOTES))

    pat = format
    for token in re.findall(TOKEN, format):
        res = re.search('([%s]?)(\$%s)([%s]?)' % \
            (quotes, token, quotes), format).groups()
        if not res:
            continue
        elif res[0] <> res[2]:
            tq = res[0] + res[2]
            tq_escaped = '\%s|\%s' % (res[0], res[2])
        else:
            tq = res[0]
            tq_escaped = '\%s' % res[0]

        tq = re.escape(tq)
        tq_escaped = re.escape(tq_escaped)

        if not tq:
            ftoken = '\$%s' % (token)
            ptoken = '(?P<' + token + '>[^\ ]*)'
        else:
            ftoken = '[%s]\$%s[%s]' % (tq, token, tq)
            ptoken = '[%s](?P<%s>(?:%s|[^%s])*)[%s]' % (tq, token, tq_escaped, tq, tq)

        pat = re.sub(ftoken, ptoken, pat)

    patc = re.compile(pat)
    def matcher(line):
        return patc.match(line)
    return matcher

def main():
    f = file('access_log')
    matcher = get_matcher(FORMAT)
    for line in f:
        print matcher(line).groupdict()

Возможно кому-нибудь будет полезным

UPD: Немного поправил код, чтобы он корректно работал с записями, в которых встречаются поля вида: “some text with escaped \” qoute”

--

Этот, а также другие мои посты по it-тематике вы можете прочитать здесь

LJ Video