Publicado em: 9 de novembro de 2023
30 min de leitura
Aprenda a programar em Python com este tutorial guiado junto com o GitLab Duo Code Suggestions e saiba uma nova linguagem de programação.

Aprender uma nova linguagem de programação pode ampliar a sua experiência no desenvolvimento de softwares, abrir portas na carreira ou simplesmente ser um desafio divertido. Porém, o problema é que decidir por uma abordagem específica para aprender essa nova linguagem nem sempre é fácil, e é aí que a inteligência artificial (IA) pode ajudar. Neste tutorial, você vai aprender a usar o GitLab Duo Code Suggestions, com tecnologia de IA, para ter uma experiência guiada de aprendizado da linguagem Python, por meio de um exemplo prático e mão na massa.
Escolha a sua IDE preferida entre as compatíveis e siga a documentação para ativar o Code Suggestions no GitLab.com SaaS ou em instâncias auto-gerenciadas do GitLab.
Algumas linguagens de programação podem exigir a instalação de ferramentas de linha de comando do interpretador, ou de compiladores que geram binários a partir do código-fonte, para compilar e executar a aplicação.
Dica: você também pode usar os workspaces de GitLab Remote Development para criar seus próprios ambientes de desenvolvimento na nuvem, em vez de usar ambientes locais. Este post foca no uso do VS Code e da GitLab Web IDE.
Instale o VS Code na sua máquina e abra o programa. Acesse o menu Extensions e procure por gitlab workflow. Instale a extensão GitLab Workflow para VS Code. O VS Code também vai detectar as linguagens de programação instaladas e sugerir plugins adicionais para destaque de sintaxe e uma melhor experiência de desenvolvimento. Por exemplo, instale a extensão Python.
Acostume-se com as sugestões antes de começar a verificá-las de fato no fluxo de trabalho. O GitLab Duo Code Suggestions fornece sugestões enquanto você digita, então não é preciso usar atalhos de teclado específicos. Para aceitar uma sugestão de código, pressione a tecla tab. Além disso, é importante notar também que escrever código novo funciona de forma mais confiável do que refatorar código já existente. Outro ponto relevante é que, como a IA é não determinística, a mesma sugestão pode não se repetir depois que você apaga um trecho de código já aceito. Enquanto o Code Suggestions está em Beta, seguimos trabalhando para melhorar a precisão geral do conteúdo gerado. Vale revisar as limitações conhecidas, já que elas podem afetar a sua experiência de aprendizado.
Dica: a versão mais recente do Code Suggestions já é compatível com instruções em várias linhas. Você pode refinar as especificações conforme a sua necessidade para obter sugestões melhores — vamos praticar esse método ao longo deste post.
Agora vamos mergulhar no aprendizado do Python, uma das linguagens compatíveis com o Code Suggestions.
Antes de entrar no código-fonte propriamente dito, configure o seu ambiente de desenvolvimento.
learn-python-ai no GitLab e clone o projeto no seu ambiente de desenvolvimento. Todos os trechos de código estão disponíveis neste projeto "Learn Python with AI". git clone https://gitlab.com/NAMESPACE/learn-python-ai.git
cd learn-python-ai
git status
brew install python
.gitignore para Python — por exemplo, este template de .gitignore para Python.Pronto, agora você já pode começar a aprender Python!
Comece a sua jornada de aprendizado pela documentação oficial e dê uma olhada nos recursos linkados, como o tutorial de Python. A documentação da biblioteca padrão e da referência da linguagem também pode ajudar bastante.
Dica: quando entrei em contato com o Python pela primeira vez, em 2005, eu não tinha muitos casos de uso além de um framework para testar drivers do Windows 2000. Anos depois, em 2016, retomei o conhecimento com o livro "Head First Python, 2nd Edition", que traz ótimos exemplos práticos para uma experiência de aprendizado completa — duas semanas depois, eu já conseguia explicar as diferenças entre Python 2 e 3. Você não precisa se preocupar com o Python 2: ele foi descontinuado há alguns anos, e vamos focar apenas no Python 3 neste post. Em agosto de 2023, foi publicado o "Head First Python, 3rd Edition", que é um ótimo recurso de aprendizado, ao lado dos exercícios compartilhados neste post.
Crie um novo arquivo hello.py no diretório raiz do projeto e comece com um comentário dizendo # Hello world. Revise e aceite a sugestão pressionando a tecla tab, e salve o arquivo (atalho de teclado: cmd s).
# Hello world
Hello World; Olá, GitLab Duo Code Suggestions
Dica: adicionar comentários no código Python começando com o caractere # antes de escrever uma função ou algoritmo ajuda o Code Suggestions a ter mais contexto para oferecer sugestões melhores. No exemplo acima, fizemos isso com # Hello world, e vamos continuar fazendo o mesmo nos próximos exercícios.
Adicione o hello.py ao Git, faça o commit de todas as alterações e envie (push) para o seu projeto no GitLab.
git add hello.py
git commit -avm "Initialize Python"
git push
O código-fonte de todos os exercícios deste post está disponível neste projeto "Learn Python with AI".
O objetivo das próximas seções é explorar os tipos de dados da linguagem, variáveis, controle de fluxo e funções. Também vamos abordar operações com arquivos, parsing de strings e operações com estruturas de dados para imprimir os resultados. Os exercícios vão ajudar a construir uma aplicação de linha de comando que lê diferentes formatos de log, trabalha com os dados e gera um resumo. Essa aplicação vai servir de base para projetos futuros que buscam logs em APIs REST, além de inspirar outras ideias, como renderizar imagens, criar um servidor web ou adicionar métricas de observabilidade.
Parsing de arquivos de log em objetos estruturados, exemplo de resultado após seguir os exercícios
Se você já é um administrador experiente, pode colocar o script em produção e usar exemplos reais de formato de log. Fazer parsing e analisar logs durante incidentes estressantes em produção pode tomar bastante tempo, e uma ferramenta de linha de comando local às vezes é mais rápida do que uma ferramenta de gestão de logs.
Vamos começar: crie um novo arquivo chamado log_reader.py no diretório raiz, adicione-o ao Git e crie um commit.
Como primeiro passo, precisamos definir a localização dos arquivos de log e o sufixo esperado do arquivo. Para isso, vamos criar duas variáveis e imprimi-las. Na prática, você pode pedir ao Code Suggestions para fazer isso, escrevendo apenas os comentários de código e aceitando as sugestões. Às vezes, é preciso experimentar diferentes sugestões e apagar blocos de código já aceitos, mas não se preocupe, a qualidade das sugestões melhora com o tempo, à medida que o modelo recebe mais contexto e gera sugestões melhores.
Defina as variáveis de caminho do log e sufixo do arquivo
Imprima as variáveis para verificar
# Specify the path and file suffix in variables
path = '/var/log/'
file_suffix = '.log'
# Print the variables
print(path)
print(file_suffix)
Acesse o terminal do VS Code e execute o script Python:
python3 log_reader.py
Terminal do VS Code, imprimindo as variáveis
O Python é compatível com muitos tipos diferentes na sua biblioteca padrão. Os tipos mais comuns são: numéricos (int, float, complex), booleanos (True, False) e strings (str). As estruturas de dados incluem suporte a listas, tuplas e dicionários.
Para praticar diferentes tipos de variáveis, vamos definir um limite de arquivos de log a serem lidos, como uma variável do tipo integer.
Variável de arquivo de log
# Define log file limit variable
log_file_limit = 1024
Crie uma variável booleana que force a leitura de todos os arquivos no diretório, independentemente do sufixo do arquivo de log.
# Define boolean variable whether to read all files recursively
read_all_files_recursively = True
Crie um diretório chamado log-data na árvore do seu projeto. Você pode copiar todos os arquivos de exemplo a partir do diretório log-data no projeto de exemplo.
Crie um novo arquivo sample.log com o seguinte conteúdo ou qualquer outra linha com uma mensagem diferente no final.
Oct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated successfully.
Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup service.
Instrua o Code Suggestions a ler o arquivo log-data/sample.log e imprimir o conteúdo.
Code Suggestions: leia o arquivo de log e o imprima
# Read the file in log-data/sample.log and print its content
with open('log-data/sample.log', 'r') as f:
print(f.read())
Dica: repare na indentação aqui. A instrução with open() as f: abre um novo escopo em que f fica disponível como stream. Esse fluxo exige indentar (tab) o bloco de código e realizar as ações dentro desse escopo, chamando f.read() para ler o conteúdo do arquivo e passando o valor imediato como parâmetro para a função print().
Volte ao terminal e execute o script novamente com python3 log_reader.py. Você vai ver o conteúdo do arquivo, já exibido no editor do VS Code, também impresso no terminal.
Terminal do VS Code: leia o arquivo de log e o imprima
Ler um único arquivo de log não é suficiente, afinal, queremos analisar todos os arquivos de um diretório de forma recursiva. Para o próximo exercício, vamos instruir o Code Suggestions a criar um índice de todos os arquivos.
Prepare o diretório log-data com mais arquivos de exemplo, a partir do diretório log-data no projeto de exemplo. A árvore do diretório deve ficar assim:
tree log-data ─╯
log-data
├── sample.log
└── var
└── log
├── auth.log
├── syslog.log
└── syslog_structured.log
3 directories, 4 files
Altere a variável path para usar o valor log-data/.
# Specify the path and file suffix in variables
path = 'log-data/'
file_suffix = '.log'
Peça ao Code Suggestions para ler todos os caminhos de arquivo do diretório em uma lista. Depois do loop de coleta, imprima a lista de caminhos dos arquivos de log.
# Read all file paths in the directory into a list
# Print the list of log file paths
Code Suggestion, coletar caminhos de arquivo
Uma sugestão possível seria algo assim:
# Read all file paths in the directory into a list
import os
# Read all file paths in the directory into a list
log_files = []
for root, directories, files in os.walk(path):
for file in files:
if file.endswith(file_suffix):
log_files.append(os.path.join(root, file))
# Print the list of log file paths
print(log_files)
Vamos entender o que acontece aqui: a instrução import os é necessária para trazer a biblioteca os para o escopo atual, permitindo chamar os.walk() mais adiante. A lista log_files é inicializada vazia, usando colchetes vazios. A função os.walk() espera receber um caminho como árvore de diretórios para começar a busca pelos arquivos, e retorna um objeto gerador, que percorre a árvore de diretórios recursivamente. As tuplas retornadas incluem os arquivos coletados, que acessamos em um loop usando a notação for. Quando um arquivo termina com o sufixo definido, seu caminho completo é adicionado à lista. Este é um exemplo já otimizado e um pouco mais complexo, então, não se preocupe em entender tudo agora, vamos retomar esse ponto mais adiante.
Modifique as instruções e adicione a avaliação da flag booleana read_all_files_recursively como entrada da função glob().
# Read all file paths in the top level directory into a list
# If read_all_files_recursively is True, read all files recursively
Code Suggestions, leia arquivos recursivamente de forma condicional
# Read all file paths in the top level directory
import os
# Read all file paths in the top level directory into a list
# If read_all_files_recursively is True, read all files recursively
log_files = []
for file in os.listdir(path):
if file.endswith(file_suffix):
log_files.append(os.path.join(path, file))
if read_all_files_recursively:
for root, directories, files in os.walk(path):
for file in files:
if file.endswith(file_suffix):
log_files.append(os.path.join(root, file))
# Print log_files
print(log_files)
O resultado ainda não é o ideal, porque o primeiro loop sempre é executado, e o segundo, opcionalmente também. Esse fluxo gera resultados duplicados quando o script é executado.
python3 log_reader.py
['log-data/sample.log', 'log-data/sample.log', 'log-data/var/log/auth.log']
Experimente diferentes instruções no Code Suggestions para chegar a uma solução para o problema. Existem algumas abordagens possíveis:
os.listdir() para o bloco else. if read_all_files_recursively:
for root, directories, files in os.walk(path):
for file in files:
if file.endswith(file_suffix):
log_files.append(os.path.join(root, file))
else:
for file in os.listdir(path):
if file.endswith(file_suffix):
log_files.append(os.path.join(path, file))
append() para sempre adicionar uma nova entrada na lista, mas verificar antes se o item já existe na lista. for file in os.listdir(path):
if file.endswith(file_suffix):
# check if the entry exists in the list already
if os.path.isfile(os.path.join(path, file)):
log_files.append(os.path.join(path, file))
if read_all_files_recursively:
for root, directories, files in os.walk(path):
for file in files:
if file.endswith(file_suffix):
# check if the entry exists in the list already
if file not in log_files:
log_files.append(os.path.join(root, file))
set(), você pode converter o set de volta para uma lista. O Code Suggestions já conhece essa possibilidade e ajuda com o comentário # Ensure that only unique file paths are in the list.
Code Suggestions, convertendo uma lista em itens únicos
# Ensure that only unique file paths are in the list
log_files = list(set(log_files))
read_all_files_recursively ainda faz sentido. É possível que o comportamento padrão devesse simplesmente ser ler todos os arquivos recursivamente?Dica para testar diferentes caminhos no VS Code: selecione os blocos de código e pressione cmd / no macOS para comentá-los.
Vamos criar uma função chamada parse_log_file, que faz o parsing de um arquivo de log e retorna os dados extraídos. Vamos definir o formato de log esperado e as colunas a serem extraídas, seguindo a especificação do formato syslog. Existem diferentes tipos de formato de log, além de formatos personalizados criados por desenvolvedores, que também precisam ser considerados. Isto fica como exercício para depois.
Inspecione uma VM Linux em execução, ou use o exemplo de arquivo de log a seguir para a implementação.
less /var/log/syslog | grep -v docker
Oct 17 00:00:04 ebpf-chaos systemd[1]: Starting Daily dpkg database backup service...
Oct 17 00:00:04 ebpf-chaos systemd[1]: Starting Rotate log files...
Oct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated successfully.
Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup service.
Oct 17 00:00:04 ebpf-chaos systemd[1]: logrotate.service: Deactivated successfully.
Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.
Oct 17 00:17:01 ebpf-chaos CRON[727495]: (root) CMD ( cd / && run-parts --report /etc/cron.hourly)
Podemos criar um algoritmo para dividir cada linha de log por espaços em branco e depois juntar os resultados novamente. Vamos pedir ajuda ao Code Suggestions.
# Split log line "Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files." by whitespaces and save in a list
log_line = "Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files."
log_line_split = log_line.split(" ")
print(log_line_split)
Execute o script novamente para verificar o resultado.
python3 log_reader.py
['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate', 'log', 'files.']
Os três primeiros itens fazem parte da string de data e hora, seguidos pelo host, o serviço e o restante dos itens da mensagem de log. Vamos praticar operações com strings em Python no próximo passo.
Vamos pedir ajuda ao Code Suggestions para aprender a juntar strings e realizar operações com listas.
Code suggestions para itens de lista com operações de string
python3 log_reader.py
# Array
['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate', 'log', 'files.']
# Dictionary
{'datetime': 'Oct 17 00:00:04', 'host': 'ebpf-chaos', 'service': 'systemd[1]:', 'message': ' ebpf-chaos systemd[1]: Finished Rotate log files.'}
Uma sugestão funcional pode ficar assim:
# Initialize results dictionary with empty values for datetime, host, service, message
# Loop over log line split
# Join the first three list items as date string
# Item 4: host
# Item 5: service
# Join the remaining items into a string, separated with whitespaces
# Print the results after the loop
results = {'datetime': '', 'host': '', 'service': '', 'message': ''}
for item in log_line_split:
if results['datetime'] == '':
results['datetime'] = ' '.join(log_line_split[0:3])
elif results['host'] == '':
results['host'] = log_line_split[3]
elif results['service'] == '':
results['service'] = log_line_split[4]
else:
results['message'] += ' ' + item
print(results)
O algoritmo sugerido percorre todos os itens da linha de log e aplica a mesma operação para os três primeiros. log_line_split[0:3] extrai uma fatia com três itens em uma nova lista. Chamar join() em um caractere separador e passar o array como argumento junta os itens em uma string. O algoritmo continua verificando se os valores de host (item 4) e serviço (item 5) ainda não foram preenchidos, e termina anexando os itens restantes da lista à string da mensagem. Para ser sincero, eu teria usado um algoritmo um pouco diferente, mas é uma boa curva de aprendizado conhecer outras formas de implementar a mesma coisa. Pratique com instruções e estruturas de dados diferentes, e continue imprimindo os conjuntos de dados.
Dica: se você precisar encerrar um script antes da hora, use sys.exit(). O restante do código não vai ser executado.
import sys
sys.exit(1)
Imagine fazer essas operações para diferentes formatos e tipos de mensagem de log — a complexidade e o risco de erro crescem rápido. Por isso, talvez exista outra abordagem.
Existem diferentes RFCs para o formato syslog: a RFC 3164 está obsoleta, mas ainda aparece na prática como configuração padrão (seguindo o padrão visto acima), enquanto a RFC 5424 é mais moderna e inclui informações de fuso horário na data e hora. Fazer o parsing desse formato pode ser complicado, então vamos pedir orientação ao Code Suggestions.
Em alguns casos, as sugestões incluem expressões regulares. Elas podem não funcionar de primeira, o que torna o código mais difícil de depurar, exigindo tentativa e erro. Um bom recurso independente para testar e entender expressões regulares é o regex101.com.
Dica: você pode evitar se aprofundar em expressões regulares usando o trecho de código a seguir como um atalho rápido. O próximo passo é instruir o Code Suggestions a usar esses padrões de log e nos ajudar a extrair todas as colunas relevantes.
# Define the syslog log format regex in a dictionary
# Add entries for RFC3164, RFC5424
regex_log_pattern = {
'rfc3164': '([A-Z][a-z][a-z]\s{1,2}\d{1,2}\s\d{2}[:]\d{2}[:]\d{2})\s([\w][\w\d\.@-]*)\s(.*)$',
'rfc5424': '(?:(\d{4}[-]\d{2}[-]\d{2}[T]\d{2}[:]\d{2}[:]\d{2}(?:\.\d{1,6})?(?:[+-]\d{2}[:]\d{2}|Z)?)|-)\s(?:([\w][\w\d\.@-]*)|-)\s(.*)$;'
}
Já sabemos o que a função deve fazer e quais são os parâmetros de entrada — o nome do arquivo e um padrão de log para dar match. As linhas de log devem ser divididas por essa expressão regular, retornando um dicionário de chave-valor para cada linha. A função deve retornar uma lista de dicionários.
# Create a function that parses a log file
# Input parameter: file path
# Match log line against regex_log_pattern
# Return the results as dictionary list: log line, pattern, extracted columns
Code suggestion baseado em uma instrução de comentário multilinha para gerar uma função que faz parsing de um arquivo de log com base em padrões regex
Lembra da indentação para abrir um novo escopo? A mesma regra vale para funções em Python. O identificador def exige um nome de função e uma lista de parâmetros, seguidos por dois-pontos de abertura. As próximas linhas de código precisam da indentação. O VS Code ajuda com o live-linting de indentação incorreta, antes que a execução do script falhe — ou os pipelines de CI/CD.
Continue com o Code Suggestions — ele já deve saber que você quer fazer o parsing de todos os arquivos de log, usando a função recém-criada.
Code suggestion para fazer parsing de todos os arquivos de log e imprimir o conjunto de resultados
Um exemplo completo e funcional pode ficar assim:
import os
# Specify the path and file suffix in variables
path = 'log-data/'
file_suffix = '.log'
# Read all file paths in the directory into a list
log_files = []
for root, directories, files in os.walk(path):
for file in files:
if file.endswith(file_suffix):
log_files.append(os.path.join(root, file))
# Define the syslog log format regex in a dictionary
# Add entries for RFC3164, RFC5424
regex_log_pattern = {
'rfc3164': '([A-Z][a-z][a-z]\s{1,2}\d{1,2}\s\d{2}[:]\d{2}[:]\d{2})\s([\w][\w\d\.@-]*)\s(.*)$',
'rfc5424': '(?:(\d{4}[-]\d{2}[-]\d{2}[T]\d{2}[:]\d{2}[:]\d{2}(?:\.\d{1,6})?(?:[+-]\d{2}[:]\d{2}|Z)?)|-)\s(?:([\w][\w\d\.@-]*)|-)\s(.*)$;'
}
# Create a function that parses a log file
# Input parameter: file path
# Match log line against regex_log_pattern
# Return the results as dictionary list: log line, pattern name, extracted columns
import re
def parse_log_file(file_path):
# Read the log file
with open(file_path, 'r') as f:
log_lines = f.readlines()
# Create a list to store the results
results = []
# Iterate over the log lines
for log_line in log_lines:
# Match the log line against the regex pattern
for pattern_name, pattern in regex_log_pattern.items():
match = re.match(pattern, log_line)
# If the log line matches the pattern, add the results to the list
if match:
extracted_columns = match.groups()
results.append({
'log_line': log_line,
'pattern_name': pattern_name,
'extracted_columns': extracted_columns,
'source_file': file_path
})
# Return the results
return results
# Parse all files and print results
for log_file in log_files:
results = parse_log_file(log_file)
print(results)
Vamos entender o que a função parse_log_file() faz:
file_path.log_lines.regex_log_pattern.log_line, pattern_name, extracted_colums e source_file.Existem diferentes variações possíveis para isso, especialmente na estrutura de dados retornada. Neste caso específico, as linhas de log já vêm como lista. Adicionar um objeto de dicionário em vez de uma linha de log bruta permite que quem chama a função extraia a informação desejada no próximo passo. Depois de implementar um exemplo funcional, você também pode refatorar o código mais tarde.
Fazer o parsing do syslog em uma distribuição Linux pode não revelar os dados necessários para a análise. Em uma máquina virtual que expõe a porta 22 (SSH) para a internet, o log de autenticação é muito mais interessante — cheio de bots e agentes maliciosos testando combinações de senha padrão, muitas vezes com ataques de força bruta.
O trecho a seguir, extraído do /var/log/auth.log de um dos meus servidores privados, mostra o formato do log de autenticação e as tentativas aleatórias de bots usando diferentes nomes de usuário, entre outras coisas.
Oct 15 00:00:19 ebpf-chaos sshd[3967944]: Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2
Oct 15 00:00:20 ebpf-chaos sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2
Oct 15 00:00:21 ebpf-chaos sshd[3967944]: Received disconnect from 93.254.246.194 port 48840:11: Bye Bye [preauth]
Oct 15 00:00:21 ebpf-chaos sshd[3967944]: Disconnected from invalid user ubuntu 93.254.246.194 port 48840 [preauth]
Oct 15 00:00:24 ebpf-chaos sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2
Oct 15 00:00:25 ebpf-chaos sshd[3967916]: Received disconnect from 180.101.88.227 port 44397:11: [preauth]
Oct 15 00:00:25 ebpf-chaos sshd[3967916]: Disconnected from authenticating user root 180.101.88.227 port 44397 [preauth]
Oct 15 00:00:25 ebpf-chaos sshd[3967916]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=180.101.88.227 user=root
Oct 15 00:00:25 ebpf-chaos sshd[3967998]: Invalid user teamspeak from 185.218.20.10 port 33436
Dica de prevenção de intrusão: configure um firewall e use o fail2ban para bloquear tentativas de login inválidas.
O próximo exercício é estender a lógica para entender as partes de texto livre da mensagem de log — por exemplo, Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2. A tarefa é armazenar esses dados em um dicionário opcional, com pares de chave-valor.
Crie uma nova função que recebe como entrada os resultados já processados das linhas de log e faz o parsing especificamente do último item da lista, em cada linha.
Failed password e Invalid user.
Code suggestions para um parser de mensagens de log que conta as falhas do auth.log
Uma sugestão funcional pode ser este código:
# Create a function that parses a log file message from the last extracted_columns entry
# Input: Parsed log lines results list
# Loop over all log lines in the list, and extract the last list item as message
# Count failure strings in the message: Failed password, Invalid user
# Return the results if failure count greater 0: log_file, count, failure string
def parse_log_file_message(results):
failure_results = []
# Iterate over the log lines
for result in results:
# Extract the message from the last list item
message = result['extracted_columns'][-1]
# Count the number of failure strings in the message
failure_count = message.count('Failed password') + message.count('Invalid user')
# If the failure count is greater than 0, add the results to the list
if failure_count > 0:
failure_results.append({
'log_file': result['source_file'],
'count': failure_count,
'failure_string': message
})
# Return the results
return failure_results
# Parse all files and print results
for log_file in log_files:
results = parse_log_file(log_file)
failure_results = parse_log_file_message(results)
print(failure_results)
O algoritmo segue as implementações anteriores: primeiro, cria um array de resultados para armazenar os dados que derem match. Depois, itera sobre as log_lines já processadas na lista. Cada linha de log contém a chave extracted_columns, que guarda a string de mensagem livre no final. O próximo passo é chamar a função count() do objeto string, para contar quantas vezes uma determinada sequência de caracteres aparece em uma string. Os números retornados são somados na variável failure_count. Se o valor for maior que zero, o resultado é adicionado à lista de resultados, incluindo os pares chave-valor log_file, count e failure_string. Depois de retornar os resultados da mensagem de log já processada, o loop percorre todos os arquivos de log, faz o parsing de cada um e imprime os resultados novamente.
Execute o script para inspecionar os matches detectados. Vale notar que a estrutura de dados ainda pode ser otimizada em passos futuros de aprendizado.
python3 log_reader.py
[{'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967944]: Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967998]: Invalid user teamspeak from 185.218.20.10 port 33436'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967998]: Failed password for invalid user teamspeak from 185.218.20.10 port 33436 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3968077]: Invalid user mcserver from 218.211.33.146 port 50950'}]
Desenvolvedores de aplicações podem usar o formato de logging estruturado para ajudar os parsers automatizados a extrair os pares chave-valor. O Prometheus fornece essa informação na seguinte estrutura, dentro do syslog:
Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.425Z caller=compact.go:519 level=info component=tsdb m
sg="write block" mint=1697558404661 maxt=1697565600000 ulid=01HCZG4ZX51GTH8H7PVBYDF4N6 duration=148.675854ms
Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.464Z caller=head.go:1213 level=info component=tsdb msg
="Head GC completed" caller=truncateMemory duration=6.845245ms
Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.467Z caller=checkpoint.go:100 level=info component=tsd
b msg="Creating checkpoint" from_segment=2308 to_segment=2309 mint=1697565600000
Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.517Z caller=head.go:1185 level=info component=tsdb msg
="WAL checkpoint complete" first=2308 last=2309 duration=50.052621ms
Esse formato é mais fácil de processar por scripts porque a parte da mensagem pode ser dividida por espaços em branco e pelo caractere de atribuição =. Aliás, é preciso entender que strings que contêm espaços em branco sempre vêm entre aspas. A desvantagem é que nem todas as bibliotecas de todas as linguagens de programação oferecem bibliotecas de logging estruturado prontas para uso, o que dificulta a adoção desse formato pelos desenvolvedores.
Pratique seguindo o exemplo anterior para fazer o parsing do formato auth.log com informações adicionais. Diga ao Code Suggestions que você espera um formato de logging estruturado com pares chave-valor, e qual estrutura de dados de retorno seria ideal:
# Create a function that parses a log file message from the last extracted_columns entry
# Input: Parsed log lines results list
# Loop over all log lines in the list, and extract the last list item as message
# Parse structured logging key-value pairs into a dictionary
# Return results: log_file, dictionary
Code suggestions para fazer parsing do formato de logging estruturado na parte de mensagem do arquivo de log
Muitos dos exemplos usaram a instrução print() para exibir o conteúdo no terminal. Os objetos Python da biblioteca padrão são compatíveis com representação em texto, e para alguns tipos isso faz mais sentido (strings, números), enquanto para outros não é possível fornecer muitos detalhes (funções, etc.).
Você também pode fazer o pretty-print de praticamente qualquer estrutura de dados (listas, sets, dicionários) em Python. A biblioteca JSON consegue formatar estruturas de dados de forma legível, usando um recuo de espaços definido para desenhar a estrutura JSON no terminal. Repare que usamos a instrução import aqui para trazer bibliotecas para o escopo atual e acessar seus métodos — por exemplo, json.dumps.
import json
print(json.dumps(structured_results, indent=4))
Parsing de arquivos de log em objetos estruturados, exemplo de resultado após seguir os exercícios
Pratique modificando o código-fonte existente e substitua os trechos de código onde fizer sentido. Outra opção é criar uma nova função que implemente o pretty printing.
# Create a pretty print function with indent 4
Code suggestions para uma função de pretty-print
Essa ideia funciona de forma parecida com a criação das suas próprias funções de logger... mas por aqui vamos parar de aprender por um momento e fazer uma pausa. Antes de encerrar o primeiro post desta série de aprendizado, vamos garantir que o CI/CD e as dependências estejam configurados corretamente para os próximos exercícios e a prática assíncrona.
As dependências podem ser gerenciadas no arquivo requirements.txt, incluindo dependências de versão opcionais. Usar o arquivo requirements.txt também tem a vantagem de ser a fonte única de verdade tanto para os ambientes de desenvolvimento local quanto para a execução de builds contínuos no GitLab CI/CD. Ambos podem usar o mesmo comando de instalação:
pip install -r requirements.txt
Algumas distribuições Linux não instalam o gerenciador de pacotes pip por padrão — no Ubuntu/Debian, por exemplo, é preciso instalar o pacote python3-pip.
Você pode gerenciar diferentes ambientes virtuais usando o venv. Esse fluxo de trabalho é útil para instalar as dependências do Python dentro do ambiente virtual, em vez de instalá-las globalmente no path do sistema operacional, o que pode gerar problemas em atualizações futuras.
pip install virtualenv
virtualenv venv
source venv/bin/activate
O pipeline de CI/CD deve continuamente fazer lint, testar e compilar o código. Você pode reproduzir as mesmas etapas do desenvolvimento local e adicionar testes para mais ambientes e versões:
stages:
- lint
- test
default:
image: python:latest
cache: # Pip's cache doesn't store the python packages
paths: # https://pip.pypa.io/en/stable/topics/caching/
- .cache/pip
before_script:
- python -V # Print out python version for debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
variables: # Change pip's cache directory to be inside the project directory since we can only cache local items.
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
# lint template
.lint-tmpl:
script:
- echo "Linting Python version $VERSION"
parallel:
matrix:
- VERSION: ['3.9', '3.10', '3.11', '3.12'] # https://hub.docker.com/_/python
# Lint, using Pyflakes: https://pypi.org/project/pyflakes/
lint-pyflakes:
extends: [.lint-tmpl]
script:
- pip install -r requirements.txt
- find . -not -path './venv' -type f -name '*.py' -exec sh -c 'pyflakes {}' \;
# Lint, using Ruff (Rust): https://docs.astral.sh/ruff/
lint-ruff:
extends: [.lint-tmpl]
script:
- pip install -r requirements.txt
- ruff .
Visualização do job de lint do Python no GitLab CI/CD, parte dos builds em matriz
Curiosidade: o GitLab Duo Code Suggestions também ajudou a escrever este post no VS Code, já que conhecia o contexto. Na captura de tela, eu só queria adicionar uma dica sobre o regex101, e o GitLab Duo já sabia o que eu ia escrever.
Escrevendo o post do blog do GitLab no VS Code com o apoio do GitLab Duo Code Suggestions
No próximo post, vamos explorar exemplos de aprendizado mais avançados, com filtragem prática de logs, operações em paralelo, como buscar logs em endpoints de API (por exemplo, logs de jobs de pipeline de CI/CD) e mais análise de dados e observabilidade. Até lá, seguem algumas recomendações para você praticar de forma assíncrona.
log_file_limit.Qual linguagem de programação você está aprendendo ou pensando em aprender? Abra um novo tópico no nosso fórum da comunidade ou no Discord e compartilhe sua experiência.
Se você usa o GitLab Duo Code Suggestions, compartilhe suas opiniões e feedback neste issue de feedback.
Comece sua avaliação gratuita de
30 dias do GitLab
Não é necessário cartão de crédito.
Gostou desta publicação, ficou com alguma dúvida ou gostaria de fazer um comentário? Compartilhe suas ideias criando um novo tópico no fórum da comunidade do GitLab.
Share your feedbackComece a desenvolver mais rápido hoje
Veja o que sua equipe pode fazer com a plataforma de orquestração inteligente para DevSecOps.