processando logs em tempo real (tail -f)

Escrever algo como um “tail -f” em python é relativamente simples. Se você tentou select(), desista, pois ele não funciona nesse caso.

O método é mais simples: guardar a posição atual do arquivo, ler a última linha; se ela não existir, fazer um seek() pra posição guardada, e aguardar algum tempo até que ele seja atualizado, e então tentar novamente.

Depois de ter a linha mais recente do arquivo, é só processá-la. Isso pode ser feito de diversas maneiras, e uma das que mais me agrada é através de um callback, pois assim posso manter a função tail genérica e usá-la em diversos casos.

Se o callback retorna False, o tail pára de ler o arquivo imediatamente, e também retorna. Caso contrário, continua lendo do arquivo especificado em loop infinito.


#!/usr/bin/env python
# coding: utf-8
# 20081023 AF

import sys, time

def tail(fd, callback, delay=1):
    fd.seek(0, 2) # seek to the end
    while 1:
        where = fd.tell()
        line = fd.readline()
        if line:
            if callback(line) is False:
                break
        else:
            time.sleep(delay)
            fd.seek(where)

if __name__ == '__main__':
    try:
        fd = file(sys.argv[1])
    except IndexError:
        print 'use: %s filename' % sys.argv[0]
        sys.exit(1)
    except (OSError, IOError), e:
        print e
        sys.exit(1)

    tail(fd, lambda x: sys.stdout.write(x))

No código acima, o callback é uma função anônima “lambda x: sys.stdout.write(x)”, que simplesmente imprime a linha ao invés de processá-la. Essa função nunca irá retornar False, portanto o loop dentro do tail será infinito.

Esse callback poderia ser algo mais elaborado, como por exemplo, ler um arquivo de log como o auth.log do Debian e identificar um usuário tentando fazer login via SSH, com senha inválida.


import re

class SSH:
    regex = re.compile(
        '\w{3} [ \d]\d \d{2}:\d{2}:\d{2} '
        '\w+ sshd[[]\d+[]]: Failed password for (invalid user )?\w+ '
        'from ([0-9.]+) port \d+ ssh2')

    def callback(self, line):
        found = self.regex.match(line)
        if not found: return

        addr = found.groups()[1]
        print 'login failure from %s' % addr

if __name__ == '__main__':
    ...

    ssh = SSH()
    tail(fd, ssh.callback)

Sempre que escrevo esses artigos, penso em uma coisa: como seria possível escrevê-los usando apenas palavras em português? Callback? Loop? “Processando registros em tempo real?”

Anúncios


Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s