Índice em arquivos python

Possuo um script em python que, ao ser executado, faz uma busca em vários arquivos de log a procura de uma palavra chave “error”, achando em algum arquivo a palavra, ele cria um novo arquivo único com nome “error_(data_atual).txt” mostrando qual o nome do arquivo com erro e qual a linha com erro, segue uma imagem e o código que uso:

 import glob
from datetime import date, datetime, timedelta

data_atual = date.today()

for f in glob.glob('jobs/*.*'):
    with open(f, "r", encoding="utf-8") as arquivo:
        texto = arquivo.readlines()

    for k, texto in enumerate(texto, start=1):
        if "erro" in texto:
            with open(f"error_{data_atual}.txt", "a", encoding="utf-8") as gravar:
                gravar.write("{} - {} {}".format(k, f, texto))

imagem
caso não carregue a imagem, link direto da imagem.

Minha intenção é criar um índice para cada arquivo que ele percorrer (dentro dessa pasta jobs) e encontrar a palavra chave, algo assim mais ou menos, que possa distinguir que o mesmo arquivo possa ter mais linhas com erro:

1 = nome do arquivo com erro
1-1 = retorno da linha com erro

1 = nome do arquivo com erro (outra linha encontrada no mesmo arquivo)
1-2 = retorno da linha com erro

2 = nome do arquivo com erro
2-1 = retorno da linha com erro

Na imagem está assim:

jobs\JOB_ONLINE_CRM_TB_ABANDONO_ANUNCIO_D0_Console.log 2022/02/01 12:00:45 - Grava Tabela In.0 - ERROR (version

  • JOB_ONLINE_CRM_TB_ABANDONO_ANUNCIO_D0_Console.log = nome do arquivo com erro
  • 2022/02/01 12:00:45 - Grava Tabela In.0 - ERROR (version 7.1.0.0-12) = retorno da linha com erro

Como mostra na imagem, tentei de algumas formas criar um índice mas ali, por exemplo, criou um índice aleatório para cada linha, nem usando a função sorted() para ordenar deu certo, como sou iniciante em python, pode ter N possibilidades de fazer isso mas como fui pesquisando e fazendo, foi como consegui, agradeço desde ja se alguém puder ajudar de alguma forma.

Nesta primeira parte estou levando em conta a mesma pergunta que vc fez no Stack Overflow…

Faltou uma coisa importante: um exemplo de como exatamente estão os arquivos.

E aí pra testar eu tive que “adivinhar”, criei dois arquivos assim para testes:

arquivo-1.log:

2022/01/31 07:15:17 - Extração Tabela.0 - ERROR (version 7.1.0.0-12, build 1 from 2017-05-16 17.18.02 by buildguy) : An error occurred, processing will be stopped: 
detalhe
mais detalhes
e mais
2022/01/31 07:15:17 - Extração Tabela.0 - Error occurred while trying to connect to the database
nao vi nada
sei la
sai daqui

arquivo-2.log:

2022/01/31 08:15:17 - Extração Tabela.1 - Error occurred while trying to connect to the database
detalhe do erro
outro detalhe

Eu entendi que as linhas que começam com uma data são as mensagens de erro, e as outras são detalhes do erro. É isso mesmo? Ou as 3 linhas que vêm depois do erro também têm a data?

Enfim, eu fiz baseado no arquivo acima, mas se o formato for outro, basta adaptar:

from glob import glob
from datetime import date

# abre o arquivo de saída apenas uma vez (afinal, todo mundo vai escrever nele, não tem porque ficar abrindo toda hora)
with open(f'error_{date.today()}.txt', 'w') as saida:
    # i é o contador do arquivo
    for i, file in enumerate(glob('jobs/*.*'), start=1):
        erro = 0 # contador do erro dentro de um arquivo, por isso zera a cada novo arquivo
        with open(file, 'r') as arquivo:
            for linha in arquivo:
                if 'error' in linha.lower():
                    erro += 1
                    data = linha[0:19]
                    msg = linha[22:]
                    msg_count = 0 # contador de mensagens relativas a este erro (zera a cada novo erro)
                    saida.write(f'{i}.{erro} - {file} {data} - {msg}')
                else: # se não é erro, então é uma das linhas de detalhe
                    msg_count += 1
                    saida.write(f'{i}.{erro}.{msg_count} - {linha}')

Repare que há 3 contadores independentes. O i é o contador do arquivo, e como esse contador itera junto com os arquivos, posso usar um enumerate no glob. Já os outros (do erro dentro de um arquivo, e da mensagem de cada erro), tem que ser independente, já que depende das condições (se tem “error” na linha ou não). O resultado ficou assim:

1.1 - jobs/arquivo-1.log 2022/01/31 07:15:17 - Extração Tabela.0 - ERROR (version 7.1.0.0-12, build 1 from 2017-05-16 17.18.02 by buildguy) : An error occurred, processing will be stopped: 
1.1.1 - detalhe
1.1.2 - mais detalhes
1.1.3 - e mais
1.2 - jobs/arquivo-1.log 2022/01/31 07:15:17 - Extração Tabela.0 - Error occurred while trying to connect to the database
1.2.1 - nao vi nada
1.2.2 - sei la
1.2.3 - sai daqui
2.1 - jobs/arquivo-2.log 2022/01/31 08:15:17 - Extração Tabela.1 - Error occurred while trying to connect to the database
2.1.1 - detalhe do erro
2.1.2 - outro detalhe

Agora, outra forma que eu entendi que talvez quem sabe possa ser, é que os arquivos estejam assim:

arquivo-1.log:

2022/01/31 07:15:17 - Extração Tabela.0 - ERROR (version 7.1.0.0-12, build 1 from 2017-05-16 17.18.02 by buildguy) : An error occurred, processing will be stopped: 
2022/01/31 07:15:17 - Extração Tabela.0 - Error occurred while trying to connect to the database

arquivo-2.log:

2022/01/31 08:15:17 - Extração Tabela.1 - Error occurred while trying to connect to the database

E cada trecho da linha é uma mensagem diferente (por exemplo, separados por hífen). Ou seja, para a linha acima, a mensagem “Extração Tabela.1” é o erro, e “Error occurred while etc…” são os detalhes. Neste caso poderia ser:

from glob import glob
from datetime import date

with open(f'error_{date.today()}.txt', 'w') as saida:
    # i é o contador do arquivo
    for i, file in enumerate(glob('jobs/*.*'), start=1):
        erro = 0 # contador do erro dentro de um arquivo, por isso zera a cada novo arquivo
        with open(file, 'r') as arquivo:
            for linha in arquivo:
                # quebra a linha em partes
                data, msg, *detalhes = linha.strip().split(' - ')
                erro += 1
                saida.write(f'{i}.{erro} - {file} {data} - {msg}\n')
                for msg_count, detalhe in enumerate(detalhes, start=1):
                    saida.write(f'{i}.{erro}.{msg_count} - {detalhe}\n')

E o resultado ficaria:

1.1 - jobs/arquivo-1.log 2022/01/31 07:15:17 - Extração Tabela.0
1.1.1 - ERROR (version 7.1.0.0-12, build 1 from 2017-05-16 17.18.02 by buildguy) : An error occurred, processing will be stopped:
1.2 - jobs/arquivo-1.log 2022/01/31 07:15:17 - Extração Tabela.0
1.2.1 - Error occurred while trying to connect to the database
2.1 - jobs/arquivo-2.log 2022/01/31 08:15:17 - Extração Tabela.1
2.1.1 - Error occurred while trying to connect to the database

Outra abordagem

Se o formato do arquivo for diferente disso, ou se você quer outro formato no resultado, melhor informar com mais detalhes (coloque várias linhas do mesmo arquivo, por exemplo, e como deveria ser a respectiva saída - de preferência como texto, pois o link com as imagens está bem lento pra mim). Sem contar que com texto é mais fácil a gente copiar e criar um arquivo para testes.

Por exemplo, lendo a pergunta de novo, dá a entender que talvez você queira o número da linha em que o erro ocorreu. Se for isso, poderia gravar apenas a linha e o arquivo, sem precisar dessa numeração mais complicada.

Assumindo que todas as linhas têm data e cada linha é uma mensagem, seria algo assim:

from glob import glob
from datetime import date

with open(f'error_{date.today()}.txt', 'w') as saida:
    for nome_arquivo in glob('jobs/*.*'):
        with open(nome_arquivo, 'r') as arquivo:
            for numero_linha, linha in enumerate(arquivo, start=1):
                saida.write(f'{numero_linha} - {nome_arquivo} - {linha}')

E aí ficaria:

1 - jobs/arquivo-1.log - 2022/01/31 07:15:17 - Extração Tabela.0 - ERROR (version 7.1.0.0-12, build 1 from 2017-05-16 17.18.02 by buildguy) : An error occurred, processing will be stopped: 
2 - jobs/arquivo-1.log - 2022/01/31 07:15:17 - Extração Tabela.0 - Error occurred while trying to connect to the database
1 - jobs/arquivo-2.log - 2022/01/31 08:15:17 - Extração Tabela.1 - Error occurred while trying to connect to the database

Ou seja, nas linhas 1 e 2 do arquivo 1, e na linha 1 do arquivo 2, ocorreram erros.


Ou ainda, se a mensagem do erro é “tudo que vem depois da data”:

from glob import glob
from datetime import date

with open(f'error_{date.today()}.txt', 'w') as saida:
    for i, file in enumerate(sorted(glob('jobs/*.*')), start=1):
        with open(file, 'r') as arquivo:
            for erro, linha in enumerate(arquivo, start=1):
                data, msg = linha.split(' - ', maxsplit=1)
                saida.write(f'{i} - {file}\n')
                saida.write(f'{i}.{erro} - {data} - {msg}')

E o resultado é:

1 - jobs/arquivo-1.log
1.1 - 2022/01/31 07:15:17 - Extração Tabela.0 - ERROR (version 7.1.0.0-12, build 1 from 2017-05-16 17.18.02 by buildguy) : An error occurred, processing will be stopped: 
1 - jobs/arquivo-1.log
1.2 - 2022/01/31 07:15:17 - Extração Tabela.0 - Error occurred while trying to connect to the database
2 - jobs/arquivo-2.log
2.1 - 2022/01/31 08:15:17 - Extração Tabela.1 - Error occurred while trying to connect to the database

Boa tarde Hugo, resolveu 110% para o que eu precisava, era exatamente isso, esse primeiro código que postou ficou show de bola o resultado, muito obrigado pela ajuda, vai facilitar demais meu dia a dia pra analisar os logs do sistema e localizar os erros, obrigado. :open_hands:

Quanto a dúvida a respeito das linhas com as datas, todas as linhas possuem data, hora e minuto que rodaram, esqueci de dar varios detalhes ali mas todas as soluções agregaram de alguma forma para o meu aprendizado