[{"data":1,"prerenderedAt":4354},["ShallowReactive",2],{"/pt-br/blog/learning-python-with-a-little-help-from-ai-code-suggestions":3,"navigation-pt-br":3626,"banner-pt-br":4051,"footer-pt-br":4058,"blog-post-authors-pt-br-Michael Friedrich":4311,"blog-related-posts-pt-br-learning-python-with-a-little-help-from-ai-code-suggestions":4326,"next-steps-pt-br":4334,"blog-share-wrapper-pt-br":4344},{"id":4,"title":5,"authors":6,"body":8,"category":3604,"date":3605,"description":3606,"extension":3607,"externalUrl":3608,"faq":3608,"featured":3609,"heroImage":3610,"meta":3611,"navigation":345,"path":3612,"seo":3613,"slug":3617,"stem":3618,"tags":3619,"template":3624,"updatedDate":3608,"__hash__":3625},"blogPosts/pt-br/blog/learning-python-with-a-little-help-from-ai-code-suggestions.md","Aprenda Python com ajuda da IA",[7],"Michael Friedrich",{"type":9,"value":10,"toc":3569},"minimark",[11,15,190,194,215,218,230,234,262,265,278,283,286,294,297,300,315,370,375,391,405,408,411,438,449,463,475,482,494,500,543,549,552,555,561,564,571,574,577,583,589,656,659,672,678,685,688,694,700,720,723,743,746,758,764,772,779,785,832,855,862,868,871,874,884,972,975,985,1011,1014,1033,1039,1042,1149,1180,1183,1190,1205,1211,1217,1220,1254,1257,1266,1354,1363,1479,1497,1503,1534,1542,1557,1560,1573,1576,1579,1585,1588,1635,1638,1702,1705,1708,1711,1730,1736,1837,1840,2127,2138,2147,2169,2172,2175,2190,2198,2203,2285,2288,2313,2319,2326,2329,2335,2338,2344,2351,2402,2405,2408,2411,2418,2424,2436,2443,2446,2460,2466,2469,2735,2763,2766,2917,2920,2923,2929,2935,2942,2969,2975,2978,2984,2993,3021,3025,3028,3037,3043,3046,3049,3052,3067,3085,3091,3100,3130,3133,3142,3171,3492,3498,3501,3509,3515,3518,3521,3541,3544,3552,3565],[12,13,14],"p",{},"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.",[16,17,18,40,60,80,86,106,144,150,170],"ul",{},[19,20,21,26],"li",{},[22,23,25],"a",{"href":24},"#preparacoes","Preparações",[16,27,28,34],{},[19,29,30],{},[22,31,33],{"href":32},"#vs-code","VS Code",[19,35,36],{},[22,37,39],{"href":38},"#code-suggestions","Code Suggestions",[19,41,42,46],{},[22,43,45],{"href":44},"#aprendendo-uma-nova-linguagem-de-programacao-python","Aprendendo uma nova linguagem de programação: Python",[16,47,48,54],{},[19,49,50],{},[22,51,53],{"href":52},"#ambiente-de-desenvolvimento-para-python","Ambiente de desenvolvimento para Python",[19,55,56],{},[22,57,59],{"href":58},"#hello-world","Hello, World",[19,61,62,66],{},[22,63,65],{"href":64},"#comece-a-aprender-python-com-um-exemplo-pratico","Comece a aprender Python com um exemplo prático",[16,67,68,74],{},[19,69,70],{},[22,71,73],{"href":72},"#defina-e-imprima-variaveis","Defina e imprima variáveis",[19,75,76],{},[22,77,79],{"href":78},"#explore-os-tipos-de-variaveis","Explore os tipos de variáveis",[19,81,82],{},[22,83,85],{"href":84},"#entrada-saida-de-arquivos-leia-e-imprima-um-arquivo-de-log","Entrada/saída de arquivos: leia e imprima um arquivo de log",[19,87,88,92],{},[22,89,91],{"href":90},"#controle-de-fluxo","Controle de fluxo",[16,93,94,100],{},[19,95,96],{},[22,97,99],{"href":98},"#loops-e-listas-para-coletar-arquivos","Loops e listas para coletar arquivos",[19,101,102],{},[22,103,105],{"href":104},"#colete-arquivos-condicionalmente","Colete arquivos condicionalmente",[19,107,108,112],{},[22,109,111],{"href":110},"#funcoes","Funções",[16,113,114,120,126,132,138],{},[19,115,116],{},[22,117,119],{"href":118},"#comece-com-um-formato-de-log-simples","Comece com um formato de log simples",[19,121,122],{},[22,123,125],{"href":124},"#operacoes-com-strings-e-estruturas-de-dados","Operações com strings e estruturas de dados",[19,127,128],{},[22,129,131],{"href":130},"#faca-o-parsing-de-arquivos-de-log-usando-expressoes-regulares","Faça o parsing de arquivos de log usando expressões regulares",[19,133,134],{},[22,135,137],{"href":136},"#formato-de-log-avancado-authlog","Formato de log avançado: auth.log",[19,139,140],{},[22,141,143],{"href":142},"#fazendo-parsing-de-mais-tipos-logging-estruturado","Fazendo parsing de mais tipos: logging estruturado",[19,145,146],{},[22,147,149],{"href":148},"#imprimindo-e-formatando-os-resultados","Imprimindo e formatando os resultados",[19,151,152,156],{},[22,153,155],{"href":154},"#gerenciamento-de-dependencias-e-verificacao-continua","Gerenciamento de dependências e verificação contínua",[16,157,158,164],{},[19,159,160],{},[22,161,163],{"href":162},"#pip-e-pyenv-trazendo-estrutura-para-o-python","Pip e pyenv: trazendo estrutura para o Python",[19,165,166],{},[22,167,169],{"href":168},"#automacao-configure-o-pipeline-de-cicd-para-python","Automação: configure o pipeline de CI/CD para Python",[19,171,172,176],{},[22,173,175],{"href":174},"#proximos-passos","Próximos passos",[16,177,178,184],{},[19,179,180],{},[22,181,183],{"href":182},"#exercicios-assincronos-de-aprendizado","Exercícios assíncronos de aprendizado",[19,185,186],{},[22,187,189],{"href":188},"#compartilhe-seu-feedback","Compartilhe seu feedback",[191,192,25],"h2",{"id":193},"preparações",[12,195,196,197,202,203,208,209,214],{},"Escolha a sua ",[22,198,201],{"href":199,"rel":200},"https://docs.gitlab.com/user/project/repository/code_suggestions/#enable-code-suggestions-in-other-ides-and-editors",[],"IDE preferida entre as compatíveis"," e siga a documentação para ativar o Code Suggestions no ",[22,204,207],{"href":205,"rel":206},"https://docs.gitlab.com/user/project/repository/code_suggestions/#enable-code-suggestions-on-gitlab-saas",[],"GitLab.com SaaS"," ou em ",[22,210,213],{"href":211,"rel":212},"https://docs.gitlab.com/user/project/repository/code_suggestions/#enable-code-suggestions-on-self-managed-gitlab",[],"instâncias auto-gerenciadas do GitLab",".",[12,216,217],{},"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.",[12,219,220,224,225,229],{},[221,222,223],"strong",{},"Dica:"," você também pode usar os ",[22,226,228],{"href":227},"/pt-br/blog/quick-start-guide-for-gitlab-workspaces/","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.",[231,232,33],"h3",{"id":233},"vs-code",[12,235,236,241,242,246,247,250,251,256,257,214],{},[22,237,240],{"href":238,"rel":239},"https://code.visualstudio.com/download",[],"Instale o VS Code"," na sua máquina e abra o programa. Acesse o menu ",[243,244,245],"code",{},"Extensions"," e procure por ",[243,248,249],{},"gitlab workflow",". Instale a ",[22,252,255],{"href":253,"rel":254},"https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow",[],"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 ",[22,258,261],{"href":259,"rel":260},"https://marketplace.visualstudio.com/items?itemName=ms-python.python",[],"extensão Python",[231,263,39],{"id":264},"code-suggestions",[12,266,267,268,271,272,277],{},"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 ",[243,269,270],{},"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 ",[22,273,276],{"href":274,"rel":275},"https://docs.gitlab.com/user/project/repository/code_suggestions/#known-limitations",[],"limitações conhecidas",", já que elas podem afetar a sua experiência de aprendizado.",[12,279,280,282],{},[221,281,223],{}," 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.",[191,284,45],{"id":285},"aprendendo-uma-nova-linguagem-de-programação-python",[12,287,288,289,214],{},"Agora vamos mergulhar no aprendizado do Python, uma das ",[22,290,293],{"href":291,"rel":292},"https://docs.gitlab.com/user/project/repository/code_suggestions/#supported-languages",[],"linguagens compatíveis com o Code Suggestions",[12,295,296],{},"Antes de entrar no código-fonte propriamente dito, configure o seu ambiente de desenvolvimento.",[231,298,53],{"id":299},"ambiente-de-desenvolvimento-para-python",[301,302,303],"ol",{},[19,304,305,306,309,310,214],{},"Crie um novo projeto ",[243,307,308],{},"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 ",[22,311,314],{"href":312,"rel":313},"https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-python-ai",[],"projeto \"Learn Python with AI\"",[316,317,322],"pre",{"className":318,"code":319,"language":320,"meta":321,"style":321},"language-shell shiki shiki-themes github-light","git clone https://gitlab.com/NAMESPACE/learn-python-ai.git\n\ncd learn-python-ai\n\ngit status\n","shell","",[243,323,324,340,347,357,362],{"__ignoreMap":321},[325,326,329,333,337],"span",{"class":327,"line":328},"line",1,[325,330,332],{"class":331},"s7eDp","git",[325,334,336],{"class":335},"sYBdl"," clone",[325,338,339],{"class":335}," https://gitlab.com/NAMESPACE/learn-python-ai.git\n",[325,341,343],{"class":327,"line":342},2,[325,344,346],{"emptyLinePlaceholder":345},true,"\n",[325,348,350,354],{"class":327,"line":349},3,[325,351,353],{"class":352},"sYu0t","cd",[325,355,356],{"class":335}," learn-python-ai\n",[325,358,360],{"class":327,"line":359},4,[325,361,346],{"emptyLinePlaceholder":345},[325,363,365,367],{"class":327,"line":364},5,[325,366,332],{"class":331},[325,368,369],{"class":335}," status\n",[301,371,372],{"start":342},[19,373,374],{},"Instale o Python e as ferramentas de build. Exemplo no macOS usando o Homebrew:",[316,376,378],{"className":318,"code":377,"language":320,"meta":321,"style":321},"brew install python\n",[243,379,380],{"__ignoreMap":321},[325,381,382,385,388],{"class":327,"line":328},[325,383,384],{"class":331},"brew",[325,386,387],{"class":335}," install",[325,389,390],{"class":335}," python\n",[301,392,393],{"start":349},[19,394,395,396,399,400,214],{},"Vale considerar adicionar um arquivo ",[243,397,398],{},".gitignore"," para Python — por exemplo, este ",[22,401,404],{"href":402,"rel":403},"https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/gitignore/Python.gitignore?ref_type=heads",[],"template de .gitignore para Python",[12,406,407],{},"Pronto, agora você já pode começar a aprender Python!",[231,409,59],{"id":410},"hello-world",[12,412,413,414,419,420,425,426,431,432,437],{},"Comece a sua jornada de aprendizado pela ",[22,415,418],{"href":416,"rel":417},"https://www.python.org/about/gettingstarted/",[],"documentação oficial"," e dê uma olhada nos recursos linkados, como o ",[22,421,424],{"href":422,"rel":423},"https://docs.python.org/3/tutorial/index.html",[],"tutorial de Python",". A documentação da ",[22,427,430],{"href":428,"rel":429},"https://docs.python.org/3/library/index.html",[],"biblioteca padrão"," e da ",[22,433,436],{"href":434,"rel":435},"https://docs.python.org/3/reference/index.html",[],"referência da linguagem"," também pode ajudar bastante.",[12,439,440,442,443,448],{},[221,441,223],{}," 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 \"",[22,444,447],{"href":445,"rel":446},"https://www.oreilly.com/library/view/head-first-python/9781492051282/",[],"Head First Python, 3rd Edition","\", que é um ótimo recurso de aprendizado, ao lado dos exercícios compartilhados neste post.",[12,450,451,452,455,456,459,460,462],{},"Crie um novo arquivo ",[243,453,454],{},"hello.py"," no diretório raiz do projeto e comece com um comentário dizendo ",[243,457,458],{},"# Hello world",". Revise e aceite a sugestão pressionando a tecla ",[243,461,270],{},", e salve o arquivo (atalho de teclado: cmd s).",[316,464,468],{"className":465,"code":466,"language":467,"meta":321,"style":321},"language-markdown shiki shiki-themes github-light","# Hello world\n","markdown",[243,469,470],{"__ignoreMap":321},[325,471,472],{"class":327,"line":328},[325,473,466],{"class":474},"surfw",[12,476,477],{},[478,479],"img",{"alt":480,"src":481,"title":480},"Hello World; Olá, GitLab Duo Code Suggestions","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398608/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_python_code_sugestions_hello_world.png",[12,483,484,486,487,490,491,493],{},[221,485,223],{}," adicionar comentários no código Python começando com o caractere ",[243,488,489],{},"#"," 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 ",[243,492,458],{},", e vamos continuar fazendo o mesmo nos próximos exercícios.",[12,495,496,497,499],{},"Adicione o ",[243,498,454],{}," ao Git, faça o commit de todas as alterações e envie (push) para o seu projeto no GitLab.",[316,501,503],{"className":318,"code":502,"language":320,"meta":321,"style":321},"git add hello.py\n\ngit commit -avm \"Initialize Python\"\n\ngit push\n",[243,504,505,515,519,532,536],{"__ignoreMap":321},[325,506,507,509,512],{"class":327,"line":328},[325,508,332],{"class":331},[325,510,511],{"class":335}," add",[325,513,514],{"class":335}," hello.py\n",[325,516,517],{"class":327,"line":342},[325,518,346],{"emptyLinePlaceholder":345},[325,520,521,523,526,529],{"class":327,"line":349},[325,522,332],{"class":331},[325,524,525],{"class":335}," commit",[325,527,528],{"class":352}," -avm",[325,530,531],{"class":335}," \"Initialize Python\"\n",[325,533,534],{"class":327,"line":359},[325,535,346],{"emptyLinePlaceholder":345},[325,537,538,540],{"class":327,"line":364},[325,539,332],{"class":331},[325,541,542],{"class":335}," push\n",[12,544,545,546,214],{},"O código-fonte de todos os exercícios deste post está disponível neste ",[22,547,314],{"href":312,"rel":548},[],[191,550,65],{"id":551},"comece-a-aprender-python-com-um-exemplo-prático",[12,553,554],{},"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.",[12,556,557],{},[478,558],{"alt":559,"src":560,"title":559},"Parsing de arquivos de log em objetos estruturados, exemplo de resultado após seguir os exercícios","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398608/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_parsing_logs_and_pretty_print_results.png",[12,562,563],{},"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.",[12,565,566,567,570],{},"Vamos começar: crie um novo arquivo chamado ",[243,568,569],{},"log_reader.py"," no diretório raiz, adicione-o ao Git e crie um commit.",[231,572,73],{"id":573},"defina-e-imprima-variáveis",[12,575,576],{},"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.",[12,578,579],{},[478,580],{"alt":581,"src":582,"title":581},"Defina as variáveis de caminho do log e sufixo do arquivo","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398604/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_01.png",[12,584,585],{},[478,586],{"alt":587,"src":588,"title":587},"Imprima as variáveis para verificar","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398604/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_02.png",[316,590,594],{"className":591,"code":592,"language":593,"meta":321,"style":321},"language-python shiki shiki-themes github-light","# Specify the path and file suffix in variables\npath = '/var/log/'\nfile_suffix = '.log'\n\n# Print the variables\n\nprint(path)\nprint(file_suffix)\n","python",[243,595,596,602,615,625,629,634,639,648],{"__ignoreMap":321},[325,597,598],{"class":327,"line":328},[325,599,601],{"class":600},"sAwPA","# Specify the path and file suffix in variables\n",[325,603,604,608,612],{"class":327,"line":342},[325,605,607],{"class":606},"sgsFI","path ",[325,609,611],{"class":610},"sD7c4","=",[325,613,614],{"class":335}," '/var/log/'\n",[325,616,617,620,622],{"class":327,"line":349},[325,618,619],{"class":606},"file_suffix ",[325,621,611],{"class":610},[325,623,624],{"class":335}," '.log'\n",[325,626,627],{"class":327,"line":359},[325,628,346],{"emptyLinePlaceholder":345},[325,630,631],{"class":327,"line":364},[325,632,633],{"class":600},"# Print the variables\n",[325,635,637],{"class":327,"line":636},6,[325,638,346],{"emptyLinePlaceholder":345},[325,640,642,645],{"class":327,"line":641},7,[325,643,644],{"class":352},"print",[325,646,647],{"class":606},"(path)\n",[325,649,651,653],{"class":327,"line":650},8,[325,652,644],{"class":352},[325,654,655],{"class":606},"(file_suffix)\n",[12,657,658],{},"Acesse o terminal do VS Code e execute o script Python:",[316,660,662],{"className":318,"code":661,"language":320,"meta":321,"style":321},"python3 log_reader.py\n",[243,663,664],{"__ignoreMap":321},[325,665,666,669],{"class":327,"line":328},[325,667,668],{"class":331},"python3",[325,670,671],{"class":335}," log_reader.py\n",[12,673,674],{},[478,675],{"alt":676,"src":677,"title":676},"Terminal do VS Code, imprimindo as variáveis","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398608/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_print_variables.png",[12,679,680,681,684],{},"O Python é compatível com muitos tipos diferentes na sua ",[22,682,430],{"href":428,"rel":683},[],". 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.",[231,686,79],{"id":687},"explore-os-tipos-de-variáveis",[12,689,690,691,214],{},"Para praticar diferentes tipos de variáveis, vamos definir um limite de arquivos de log a serem lidos, como uma variável do tipo ",[243,692,693],{},"integer",[12,695,696],{},[478,697],{"alt":698,"src":699,"title":698},"Variável de arquivo de log","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398605/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_03.png",[316,701,703],{"className":591,"code":702,"language":593,"meta":321,"style":321},"# Define log file limit variable\nlog_file_limit = 1024\n",[243,704,705,710],{"__ignoreMap":321},[325,706,707],{"class":327,"line":328},[325,708,709],{"class":600},"# Define log file limit variable\n",[325,711,712,715,717],{"class":327,"line":342},[325,713,714],{"class":606},"log_file_limit ",[325,716,611],{"class":610},[325,718,719],{"class":352}," 1024\n",[12,721,722],{},"Crie uma variável booleana que force a leitura de todos os arquivos no diretório, independentemente do sufixo do arquivo de log.",[316,724,726],{"className":591,"code":725,"language":593,"meta":321,"style":321},"# Define boolean variable whether to read all files recursively\nread_all_files_recursively = True\n",[243,727,728,733],{"__ignoreMap":321},[325,729,730],{"class":327,"line":328},[325,731,732],{"class":600},"# Define boolean variable whether to read all files recursively\n",[325,734,735,738,740],{"class":327,"line":342},[325,736,737],{"class":606},"read_all_files_recursively ",[325,739,611],{"class":610},[325,741,742],{"class":352}," True\n",[191,744,85],{"id":745},"entradasaída-de-arquivos-leia-e-imprima-um-arquivo-de-log",[12,747,748,749,752,753,214],{},"Crie um diretório chamado ",[243,750,751],{},"log-data"," na árvore do seu projeto. Você pode copiar todos os arquivos de exemplo a partir do ",[22,754,757],{"href":755,"rel":756},"https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-python-ai/-/tree/main/log-data?ref_type=heads",[],"diretório log-data no projeto de exemplo",[12,759,451,760,763],{},[243,761,762],{},"sample.log"," com o seguinte conteúdo ou qualquer outra linha com uma mensagem diferente no final.",[316,765,770],{"className":766,"code":768,"language":769,"meta":321},[767],"language-text","Oct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated successfully.\nOct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup service.\n","text",[243,771,768],{"__ignoreMap":321},[12,773,774,775,778],{},"Instrua o Code Suggestions a ler o arquivo ",[243,776,777],{},"log-data/sample.log"," e imprimir o conteúdo.",[12,780,781],{},[478,782],{"alt":783,"src":784,"title":783},"Code Suggestions: leia o arquivo de log e o imprima","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398607/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_read_log_file_and_print.png",[316,786,788],{"className":591,"code":787,"language":593,"meta":321,"style":321},"# Read the file in log-data/sample.log and print its content\nwith open('log-data/sample.log', 'r') as f:\n    print(f.read())\n\n",[243,789,790,795,824],{"__ignoreMap":321},[325,791,792],{"class":327,"line":328},[325,793,794],{"class":600},"# Read the file in log-data/sample.log and print its content\n",[325,796,797,800,803,806,809,812,815,818,821],{"class":327,"line":342},[325,798,799],{"class":610},"with",[325,801,802],{"class":352}," open",[325,804,805],{"class":606},"(",[325,807,808],{"class":335},"'log-data/sample.log'",[325,810,811],{"class":606},", ",[325,813,814],{"class":335},"'r'",[325,816,817],{"class":606},") ",[325,819,820],{"class":610},"as",[325,822,823],{"class":606}," f:\n",[325,825,826,829],{"class":327,"line":349},[325,827,828],{"class":352},"    print",[325,830,831],{"class":606},"(f.read())\n",[12,833,834,836,837,840,841,844,845,847,848,851,852,214],{},[221,835,223],{}," repare na indentação aqui. A instrução ",[243,838,839],{},"with open() as f:"," abre um novo escopo em que ",[243,842,843],{},"f"," fica disponível como stream. Esse fluxo exige indentar (",[243,846,270],{},") o bloco de código e realizar as ações dentro desse escopo, chamando ",[243,849,850],{},"f.read()"," para ler o conteúdo do arquivo e passando o valor imediato como parâmetro para a função ",[243,853,854],{},"print()",[12,856,857,858,861],{},"Volte ao terminal e execute o script novamente com ",[243,859,860],{},"python3 log_reader.py",". Você vai ver o conteúdo do arquivo, já exibido no editor do VS Code, também impresso no terminal.",[12,863,864],{},[478,865],{"alt":866,"src":867,"title":866},"Terminal do VS Code: leia o arquivo de log e o imprima","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398608/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_print_logfile_show_file_sample.png",[191,869,91],{"id":870},"controle-de-fluxo",[12,872,873],{},"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.",[12,875,876,877,879,880,883],{},"Prepare o diretório ",[243,878,751],{}," com mais arquivos de exemplo, a partir do ",[22,881,757],{"href":755,"rel":882},[],". A árvore do diretório deve ficar assim:",[316,885,887],{"className":318,"code":886,"language":320,"meta":321,"style":321},"tree log-data                                                             ─╯\nlog-data\n├── sample.log\n└── var\n    └── log\n        ├── auth.log\n        ├── syslog.log\n        └── syslog_structured.log\n\n3 directories, 4 files\n",[243,888,889,900,905,913,921,929,937,944,952,957],{"__ignoreMap":321},[325,890,891,894,897],{"class":327,"line":328},[325,892,893],{"class":331},"tree",[325,895,896],{"class":335}," log-data",[325,898,899],{"class":335},"                                                             ─╯\n",[325,901,902],{"class":327,"line":342},[325,903,904],{"class":331},"log-data\n",[325,906,907,910],{"class":327,"line":349},[325,908,909],{"class":331},"├──",[325,911,912],{"class":335}," sample.log\n",[325,914,915,918],{"class":327,"line":359},[325,916,917],{"class":331},"└──",[325,919,920],{"class":335}," var\n",[325,922,923,926],{"class":327,"line":364},[325,924,925],{"class":331},"    └──",[325,927,928],{"class":335}," log\n",[325,930,931,934],{"class":327,"line":636},[325,932,933],{"class":331},"        ├──",[325,935,936],{"class":335}," auth.log\n",[325,938,939,941],{"class":327,"line":641},[325,940,933],{"class":331},[325,942,943],{"class":335}," syslog.log\n",[325,945,946,949],{"class":327,"line":650},[325,947,948],{"class":331},"        └──",[325,950,951],{"class":335}," syslog_structured.log\n",[325,953,955],{"class":327,"line":954},9,[325,956,346],{"emptyLinePlaceholder":345},[325,958,960,963,966,969],{"class":327,"line":959},10,[325,961,962],{"class":331},"3",[325,964,965],{"class":335}," directories,",[325,967,968],{"class":352}," 4",[325,970,971],{"class":335}," files\n",[231,973,99],{"id":974},"loops-e-listas-para-coletar-arquivos",[12,976,977,978,981,982,214],{},"Altere a variável ",[243,979,980],{},"path"," para usar o valor ",[243,983,984],{},"log-data/",[316,986,988],{"className":591,"code":987,"language":593,"meta":321,"style":321},"# Specify the path and file suffix in variables\npath = 'log-data/'\nfile_suffix = '.log'\n",[243,989,990,994,1003],{"__ignoreMap":321},[325,991,992],{"class":327,"line":328},[325,993,601],{"class":600},[325,995,996,998,1000],{"class":327,"line":342},[325,997,607],{"class":606},[325,999,611],{"class":610},[325,1001,1002],{"class":335}," 'log-data/'\n",[325,1004,1005,1007,1009],{"class":327,"line":349},[325,1006,619],{"class":606},[325,1008,611],{"class":610},[325,1010,624],{"class":335},[12,1012,1013],{},"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.",[316,1015,1017],{"className":591,"code":1016,"language":593,"meta":321,"style":321},"# Read all file paths in the directory into a list\n\n# Print the list of log file paths\n",[243,1018,1019,1024,1028],{"__ignoreMap":321},[325,1020,1021],{"class":327,"line":328},[325,1022,1023],{"class":600},"# Read all file paths in the directory into a list\n",[325,1025,1026],{"class":327,"line":342},[325,1027,346],{"emptyLinePlaceholder":345},[325,1029,1030],{"class":327,"line":349},[325,1031,1032],{"class":600},"# Print the list of log file paths\n",[12,1034,1035],{},[478,1036],{"alt":1037,"src":1038,"title":1037},"Code Suggestion, coletar caminhos de arquivo","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398601/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_collect_files.png",[12,1040,1041],{},"Uma sugestão possível seria algo assim:",[316,1043,1045],{"className":591,"code":1044,"language":593,"meta":321,"style":321},"# Read all file paths in the directory into a list\nimport os\n\n# Read all file paths in the directory into a list\nlog_files = []\nfor root, directories, files in os.walk(path):\n    for file in files:\n        if file.endswith(file_suffix):\n            log_files.append(os.path.join(root, file))\n\n# Print the list of log file paths\n\nprint(log_files)\n",[243,1046,1047,1051,1059,1063,1067,1077,1091,1106,1116,1127,1131,1136,1141],{"__ignoreMap":321},[325,1048,1049],{"class":327,"line":328},[325,1050,1023],{"class":600},[325,1052,1053,1056],{"class":327,"line":342},[325,1054,1055],{"class":610},"import",[325,1057,1058],{"class":606}," os\n",[325,1060,1061],{"class":327,"line":349},[325,1062,346],{"emptyLinePlaceholder":345},[325,1064,1065],{"class":327,"line":359},[325,1066,1023],{"class":600},[325,1068,1069,1072,1074],{"class":327,"line":364},[325,1070,1071],{"class":606},"log_files ",[325,1073,611],{"class":610},[325,1075,1076],{"class":606}," []\n",[325,1078,1079,1082,1085,1088],{"class":327,"line":636},[325,1080,1081],{"class":610},"for",[325,1083,1084],{"class":606}," root, directories, files ",[325,1086,1087],{"class":610},"in",[325,1089,1090],{"class":606}," os.walk(path):\n",[325,1092,1093,1096,1100,1103],{"class":327,"line":641},[325,1094,1095],{"class":610},"    for",[325,1097,1099],{"class":1098},"sqxcx"," file",[325,1101,1102],{"class":610}," in",[325,1104,1105],{"class":606}," files:\n",[325,1107,1108,1111,1113],{"class":327,"line":650},[325,1109,1110],{"class":610},"        if",[325,1112,1099],{"class":1098},[325,1114,1115],{"class":606},".endswith(file_suffix):\n",[325,1117,1118,1121,1124],{"class":327,"line":954},[325,1119,1120],{"class":606},"            log_files.append(os.path.join(root, ",[325,1122,1123],{"class":1098},"file",[325,1125,1126],{"class":606},"))\n",[325,1128,1129],{"class":327,"line":959},[325,1130,346],{"emptyLinePlaceholder":345},[325,1132,1134],{"class":327,"line":1133},11,[325,1135,1032],{"class":600},[325,1137,1139],{"class":327,"line":1138},12,[325,1140,346],{"emptyLinePlaceholder":345},[325,1142,1144,1146],{"class":327,"line":1143},13,[325,1145,644],{"class":352},[325,1147,1148],{"class":606},"(log_files)\n",[12,1150,1151,1152,1155,1156,1159,1160,1163,1164,1169,1170,1173,1174,1176,1177,1179],{},"Vamos entender o que acontece aqui: a instrução ",[243,1153,1154],{},"import os"," é necessária para trazer a biblioteca ",[243,1157,1158],{},"os"," para o escopo atual, permitindo chamar ",[243,1161,1162],{},"os.walk()"," mais adiante. A ",[22,1165,1168],{"href":1166,"rel":1167},"https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range",[],"lista"," ",[243,1171,1172],{},"log_files"," é inicializada vazia, usando colchetes vazios. A função ",[243,1175,1162],{}," 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 ",[243,1178,1081],{},". 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.",[231,1181,105],{"id":1182},"colete-arquivos-condicionalmente",[12,1184,1185,1186,1189],{},"Modifique as instruções e adicione a avaliação da flag booleana ",[243,1187,1188],{},"read_all_files_recursively"," como entrada da função glob().",[316,1191,1193],{"className":591,"code":1192,"language":593,"meta":321,"style":321},"# Read all file paths in the top level directory into a list\n# If read_all_files_recursively is True, read all files recursively\n",[243,1194,1195,1200],{"__ignoreMap":321},[325,1196,1197],{"class":327,"line":328},[325,1198,1199],{"class":600},"# Read all file paths in the top level directory into a list\n",[325,1201,1202],{"class":327,"line":342},[325,1203,1204],{"class":600},"# If read_all_files_recursively is True, read all files recursively\n",[12,1206,1207],{},[478,1208],{"alt":1209,"src":1210,"title":1209},"Code Suggestions, leia arquivos recursivamente de forma condicional","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398607/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_read_files_recursively_conditionally.png",[316,1212,1215],{"className":1213,"code":1214,"language":769,"meta":321},[767],"# Read all file paths in the top level directory\nimport os\n\n# Read all file paths in the top level directory into a list\n# If read_all_files_recursively is True, read all files recursively\n\nlog_files = []\nfor file in os.listdir(path):\n    if file.endswith(file_suffix):\n        log_files.append(os.path.join(path, file))\n\nif read_all_files_recursively:\n    for root, directories, files in os.walk(path):\n        for file in files:\n            if file.endswith(file_suffix):\n                log_files.append(os.path.join(root, file))\n\n# Print log_files\nprint(log_files)\n",[243,1216,1214],{"__ignoreMap":321},[12,1218,1219],{},"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.",[316,1221,1223],{"className":318,"code":1222,"language":320,"meta":321,"style":321},"python3 log_reader.py\n\n['log-data/sample.log', 'log-data/sample.log', 'log-data/var/log/auth.log']\n",[243,1224,1225,1231,1235],{"__ignoreMap":321},[325,1226,1227,1229],{"class":327,"line":328},[325,1228,668],{"class":331},[325,1230,671],{"class":335},[325,1232,1233],{"class":327,"line":342},[325,1234,346],{"emptyLinePlaceholder":345},[325,1236,1237,1240,1242,1244,1246,1248,1251],{"class":327,"line":349},[325,1238,1239],{"class":606},"[",[325,1241,808],{"class":335},[325,1243,811],{"class":606},[325,1245,808],{"class":335},[325,1247,811],{"class":606},[325,1249,1250],{"class":335},"'log-data/var/log/auth.log'",[325,1252,1253],{"class":606},"]\n",[12,1255,1256],{},"Experimente diferentes instruções no Code Suggestions para chegar a uma solução para o problema. Existem algumas abordagens possíveis:",[301,1258,1259],{},[19,1260,1261,1262,1265],{},"Uma solução possível é envolver o código em um bloco if-then-else, movendo o loop ",[243,1263,1264],{},"os.listdir()"," para o bloco else.",[316,1267,1269],{"className":591,"code":1268,"language":593,"meta":321,"style":321},"if read_all_files_recursively:\n    for root, directories, files in os.walk(path):\n        for file in files:\n            if file.endswith(file_suffix):\n                log_files.append(os.path.join(root, file))\nelse:\n    for file in os.listdir(path):\n        if file.endswith(file_suffix):\n            log_files.append(os.path.join(path, file))\n\n",[243,1270,1271,1279,1289,1300,1309,1318,1326,1337,1345],{"__ignoreMap":321},[325,1272,1273,1276],{"class":327,"line":328},[325,1274,1275],{"class":610},"if",[325,1277,1278],{"class":606}," read_all_files_recursively:\n",[325,1280,1281,1283,1285,1287],{"class":327,"line":342},[325,1282,1095],{"class":610},[325,1284,1084],{"class":606},[325,1286,1087],{"class":610},[325,1288,1090],{"class":606},[325,1290,1291,1294,1296,1298],{"class":327,"line":349},[325,1292,1293],{"class":610},"        for",[325,1295,1099],{"class":1098},[325,1297,1102],{"class":610},[325,1299,1105],{"class":606},[325,1301,1302,1305,1307],{"class":327,"line":359},[325,1303,1304],{"class":610},"            if",[325,1306,1099],{"class":1098},[325,1308,1115],{"class":606},[325,1310,1311,1314,1316],{"class":327,"line":364},[325,1312,1313],{"class":606},"                log_files.append(os.path.join(root, ",[325,1315,1123],{"class":1098},[325,1317,1126],{"class":606},[325,1319,1320,1323],{"class":327,"line":636},[325,1321,1322],{"class":610},"else",[325,1324,1325],{"class":606},":\n",[325,1327,1328,1330,1332,1334],{"class":327,"line":641},[325,1329,1095],{"class":610},[325,1331,1099],{"class":1098},[325,1333,1102],{"class":610},[325,1335,1336],{"class":606}," os.listdir(path):\n",[325,1338,1339,1341,1343],{"class":327,"line":650},[325,1340,1110],{"class":610},[325,1342,1099],{"class":1098},[325,1344,1115],{"class":606},[325,1346,1347,1350,1352],{"class":327,"line":954},[325,1348,1349],{"class":606},"            log_files.append(os.path.join(path, ",[325,1351,1123],{"class":1098},[325,1353,1126],{"class":606},[301,1355,1356],{"start":342},[19,1357,1358,1359,1362],{},"Outra opção é não usar ",[243,1360,1361],{},"append()"," para sempre adicionar uma nova entrada na lista, mas verificar antes se o item já existe na lista.",[316,1364,1366],{"className":591,"code":1365,"language":593,"meta":321,"style":321},"for file in os.listdir(path):\n    if file.endswith(file_suffix):\n        # check if the entry exists in the list already\n        if os.path.isfile(os.path.join(path, file)):\n            log_files.append(os.path.join(path, file))\n\nif read_all_files_recursively:\n    for root, directories, files in os.walk(path):\n        for file in files:\n            if file.endswith(file_suffix):\n                # check if the entry exists in the list already\n                if file not in log_files:\n                    log_files.append(os.path.join(root, file))\n\n",[243,1367,1368,1378,1387,1392,1404,1412,1416,1422,1432,1442,1450,1455,1470],{"__ignoreMap":321},[325,1369,1370,1372,1374,1376],{"class":327,"line":328},[325,1371,1081],{"class":610},[325,1373,1099],{"class":1098},[325,1375,1102],{"class":610},[325,1377,1336],{"class":606},[325,1379,1380,1383,1385],{"class":327,"line":342},[325,1381,1382],{"class":610},"    if",[325,1384,1099],{"class":1098},[325,1386,1115],{"class":606},[325,1388,1389],{"class":327,"line":349},[325,1390,1391],{"class":600},"        # check if the entry exists in the list already\n",[325,1393,1394,1396,1399,1401],{"class":327,"line":359},[325,1395,1110],{"class":610},[325,1397,1398],{"class":606}," os.path.isfile(os.path.join(path, ",[325,1400,1123],{"class":1098},[325,1402,1403],{"class":606},")):\n",[325,1405,1406,1408,1410],{"class":327,"line":364},[325,1407,1349],{"class":606},[325,1409,1123],{"class":1098},[325,1411,1126],{"class":606},[325,1413,1414],{"class":327,"line":636},[325,1415,346],{"emptyLinePlaceholder":345},[325,1417,1418,1420],{"class":327,"line":641},[325,1419,1275],{"class":610},[325,1421,1278],{"class":606},[325,1423,1424,1426,1428,1430],{"class":327,"line":650},[325,1425,1095],{"class":610},[325,1427,1084],{"class":606},[325,1429,1087],{"class":610},[325,1431,1090],{"class":606},[325,1433,1434,1436,1438,1440],{"class":327,"line":954},[325,1435,1293],{"class":610},[325,1437,1099],{"class":1098},[325,1439,1102],{"class":610},[325,1441,1105],{"class":606},[325,1443,1444,1446,1448],{"class":327,"line":959},[325,1445,1304],{"class":610},[325,1447,1099],{"class":1098},[325,1449,1115],{"class":606},[325,1451,1452],{"class":327,"line":1133},[325,1453,1454],{"class":600},"                # check if the entry exists in the list already\n",[325,1456,1457,1460,1462,1465,1467],{"class":327,"line":1138},[325,1458,1459],{"class":610},"                if",[325,1461,1099],{"class":1098},[325,1463,1464],{"class":610}," not",[325,1466,1102],{"class":610},[325,1468,1469],{"class":606}," log_files:\n",[325,1471,1472,1475,1477],{"class":327,"line":1143},[325,1473,1474],{"class":606},"                    log_files.append(os.path.join(root, ",[325,1476,1123],{"class":1098},[325,1478,1126],{"class":606},[301,1480,1481],{"start":349},[19,1482,1483,1484,1489,1490,1493,1494,214],{},"Ou então, podemos eliminar as entradas duplicadas depois de coletar todos os itens. O Python permite converter listas em ",[22,1485,1488],{"href":1486,"rel":1487},"https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset",[],"sets",", que armazenam apenas entradas únicas. Depois de aplicar ",[243,1491,1492],{},"set()",", você pode converter o set de volta para uma lista. O Code Suggestions já conhece essa possibilidade e ajuda com o comentário ",[243,1495,1496],{},"# Ensure that only unique file paths are in the list",[12,1498,1499],{},[478,1500],{"alt":1501,"src":1502,"title":1501},"Code Suggestions, convertendo uma lista em itens únicos","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398607/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_unique_list.png",[316,1504,1506],{"className":591,"code":1505,"language":593,"meta":321,"style":321},"# Ensure that only unique file paths are in the list\n\nlog_files = list(set(log_files))\n",[243,1507,1508,1513,1517],{"__ignoreMap":321},[325,1509,1510],{"class":327,"line":328},[325,1511,1512],{"class":600},"# Ensure that only unique file paths are in the list\n",[325,1514,1515],{"class":327,"line":342},[325,1516,346],{"emptyLinePlaceholder":345},[325,1518,1519,1521,1523,1526,1528,1531],{"class":327,"line":349},[325,1520,1071],{"class":606},[325,1522,611],{"class":610},[325,1524,1525],{"class":352}," list",[325,1527,805],{"class":606},[325,1529,1530],{"class":352},"set",[325,1532,1533],{"class":606},"(log_files))\n",[301,1535,1536],{"start":359},[19,1537,1538,1539,1541],{},"Dê um passo atrás e avalie se a variável ",[243,1540,1188],{}," ainda faz sentido. É possível que o comportamento padrão devesse simplesmente ser ler todos os arquivos recursivamente?",[12,1543,1544,1547,1548,1556],{},[221,1545,1546],{},"Dica para testar diferentes caminhos no VS Code:"," selecione os blocos de código e pressione ",[22,1549,1552,1555],{"href":1550,"rel":1551},"https://code.visualstudio.com/docs/getstarted/keybindings",[],[243,1553,1554],{},"cmd /"," no macOS"," para comentá-los.",[191,1558,111],{"id":1559},"funções",[12,1561,1562,1563,1566,1567,1572],{},"Vamos criar uma função chamada ",[243,1564,1565],{},"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 ",[22,1568,1571],{"href":1569,"rel":1570},"https://en.wikipedia.org/wiki/Syslog",[],"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.",[231,1574,119],{"id":1575},"comece-com-um-formato-de-log-simples",[12,1577,1578],{},"Inspecione uma VM Linux em execução, ou use o exemplo de arquivo de log a seguir para a implementação.",[316,1580,1583],{"className":1581,"code":1582,"language":769,"meta":321},[767],"less /var/log/syslog | grep -v docker\n\nOct 17 00:00:04 ebpf-chaos systemd[1]: Starting Daily dpkg database backup service...\nOct 17 00:00:04 ebpf-chaos systemd[1]: Starting Rotate log files...\nOct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated successfully.\nOct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup service.\nOct 17 00:00:04 ebpf-chaos systemd[1]: logrotate.service: Deactivated successfully.\nOct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\nOct 17 00:17:01 ebpf-chaos CRON[727495]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)\n",[243,1584,1582],{"__ignoreMap":321},[12,1586,1587],{},"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.",[316,1589,1591],{"className":591,"code":1590,"language":593,"meta":321,"style":321},"# Split log line \"Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\" by whitespaces and save in a list\n\nlog_line = \"Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\"\nlog_line_split = log_line.split(\" \")\nprint(log_line_split)\n",[243,1592,1593,1598,1602,1612,1628],{"__ignoreMap":321},[325,1594,1595],{"class":327,"line":328},[325,1596,1597],{"class":600},"# Split log line \"Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\" by whitespaces and save in a list\n",[325,1599,1600],{"class":327,"line":342},[325,1601,346],{"emptyLinePlaceholder":345},[325,1603,1604,1607,1609],{"class":327,"line":349},[325,1605,1606],{"class":606},"log_line ",[325,1608,611],{"class":610},[325,1610,1611],{"class":335}," \"Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\"\n",[325,1613,1614,1617,1619,1622,1625],{"class":327,"line":359},[325,1615,1616],{"class":606},"log_line_split ",[325,1618,611],{"class":610},[325,1620,1621],{"class":606}," log_line.split(",[325,1623,1624],{"class":335},"\" \"",[325,1626,1627],{"class":606},")\n",[325,1629,1630,1632],{"class":327,"line":364},[325,1631,644],{"class":352},[325,1633,1634],{"class":606},"(log_line_split)\n",[12,1636,1637],{},"Execute o script novamente para verificar o resultado.",[316,1639,1641],{"className":318,"code":1640,"language":320,"meta":321,"style":321},"python3 log_reader.py\n\n['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate', 'log', 'files.']\n",[243,1642,1643,1649,1653],{"__ignoreMap":321},[325,1644,1645,1647],{"class":327,"line":328},[325,1646,668],{"class":331},[325,1648,671],{"class":335},[325,1650,1651],{"class":327,"line":342},[325,1652,346],{"emptyLinePlaceholder":345},[325,1654,1655,1657,1660,1662,1665,1667,1670,1672,1675,1677,1680,1682,1685,1687,1690,1692,1695,1697,1700],{"class":327,"line":349},[325,1656,1239],{"class":606},[325,1658,1659],{"class":335},"'Oct'",[325,1661,811],{"class":606},[325,1663,1664],{"class":335},"'17'",[325,1666,811],{"class":606},[325,1668,1669],{"class":335},"'00:00:04'",[325,1671,811],{"class":606},[325,1673,1674],{"class":335},"'ebpf-chaos'",[325,1676,811],{"class":606},[325,1678,1679],{"class":335},"'systemd[1]:'",[325,1681,811],{"class":606},[325,1683,1684],{"class":335},"'Finished'",[325,1686,811],{"class":606},[325,1688,1689],{"class":335},"'Rotate'",[325,1691,811],{"class":606},[325,1693,1694],{"class":335},"'log'",[325,1696,811],{"class":606},[325,1698,1699],{"class":335},"'files.'",[325,1701,1253],{"class":606},[12,1703,1704],{},"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.",[231,1706,125],{"id":1707},"operações-com-strings-e-estruturas-de-dados",[12,1709,1710],{},"Vamos pedir ajuda ao Code Suggestions para aprender a juntar strings e realizar operações com listas.",[301,1712,1713,1716,1719,1722],{},[19,1714,1715],{},"Junte os três primeiros itens novamente, separados por espaço.",[19,1717,1718],{},"Mantenha o host e o serviço.",[19,1720,1721],{},"Junte a contagem de itens variável restante em uma string, novamente separada por espaços.",[19,1723,1724,1725,214],{},"Armazene as chaves de coluna identificadas e seus respectivos valores em uma nova estrutura de dados: um ",[22,1726,1729],{"href":1727,"rel":1728},"https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",[],"dicionário",[12,1731,1732],{},[478,1733],{"alt":1734,"src":1735,"title":1734},"Code suggestions para itens de lista com operações de string","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398602/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_list_items_string_join_extract.png",[316,1737,1739],{"className":318,"code":1738,"language":320,"meta":321,"style":321},"python3 log_reader.py\n\n# Array\n['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate', 'log', 'files.']\n\n# Dictionary\n{'datetime': 'Oct 17 00:00:04', 'host': 'ebpf-chaos', 'service': 'systemd[1]:', 'message': ' ebpf-chaos systemd[1]: Finished Rotate log files.'}\n",[243,1740,1741,1747,1751,1756,1796,1800,1805],{"__ignoreMap":321},[325,1742,1743,1745],{"class":327,"line":328},[325,1744,668],{"class":331},[325,1746,671],{"class":335},[325,1748,1749],{"class":327,"line":342},[325,1750,346],{"emptyLinePlaceholder":345},[325,1752,1753],{"class":327,"line":349},[325,1754,1755],{"class":600},"# Array\n",[325,1757,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792,1794],{"class":327,"line":359},[325,1759,1239],{"class":606},[325,1761,1659],{"class":335},[325,1763,811],{"class":606},[325,1765,1664],{"class":335},[325,1767,811],{"class":606},[325,1769,1669],{"class":335},[325,1771,811],{"class":606},[325,1773,1674],{"class":335},[325,1775,811],{"class":606},[325,1777,1679],{"class":335},[325,1779,811],{"class":606},[325,1781,1684],{"class":335},[325,1783,811],{"class":606},[325,1785,1689],{"class":335},[325,1787,811],{"class":606},[325,1789,1694],{"class":335},[325,1791,811],{"class":606},[325,1793,1699],{"class":335},[325,1795,1253],{"class":606},[325,1797,1798],{"class":327,"line":364},[325,1799,346],{"emptyLinePlaceholder":345},[325,1801,1802],{"class":327,"line":636},[325,1803,1804],{"class":600},"# Dictionary\n",[325,1806,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834],{"class":327,"line":641},[325,1808,1809],{"class":606},"{",[325,1811,1812],{"class":331},"'datetime'",[325,1814,1815],{"class":352},":",[325,1817,1818],{"class":335}," 'Oct 17 00:00:04',",[325,1820,1821],{"class":335}," 'host':",[325,1823,1824],{"class":335}," 'ebpf-chaos',",[325,1826,1827],{"class":335}," 'service':",[325,1829,1830],{"class":335}," 'systemd[1]:',",[325,1832,1833],{"class":335}," 'message':",[325,1835,1836],{"class":335}," ' ebpf-chaos systemd[1]: Finished Rotate log files.'}\n",[12,1838,1839],{},"Uma sugestão funcional pode ficar assim:",[316,1841,1843],{"className":591,"code":1842,"language":593,"meta":321,"style":321},"# Initialize results dictionary with empty values for datetime, host, service, message\n# Loop over log line split\n# Join the first three list items as date string\n# Item 4: host\n# Item 5: service\n# Join the remaining items into a string, separated with whitespaces\n# Print the results after the loop\n\nresults = {'datetime': '', 'host': '', 'service': '', 'message': ''}\n\nfor item in log_line_split:\n\n    if results['datetime'] == '':\n        results['datetime'] = ' '.join(log_line_split[0:3])\n\n    elif results['host'] == '':\n        results['host'] = log_line_split[3]\n\n    elif results['service'] == '':\n        results['service'] = log_line_split[4]\n\n    else:\n        results['message'] += ' ' + item\n\nprint(results)\n",[243,1844,1845,1850,1855,1860,1865,1870,1875,1880,1884,1932,1936,1948,1952,1972,2000,2005,2023,2041,2046,2063,2081,2086,2094,2114,2119],{"__ignoreMap":321},[325,1846,1847],{"class":327,"line":328},[325,1848,1849],{"class":600},"# Initialize results dictionary with empty values for datetime, host, service, message\n",[325,1851,1852],{"class":327,"line":342},[325,1853,1854],{"class":600},"# Loop over log line split\n",[325,1856,1857],{"class":327,"line":349},[325,1858,1859],{"class":600},"# Join the first three list items as date string\n",[325,1861,1862],{"class":327,"line":359},[325,1863,1864],{"class":600},"# Item 4: host\n",[325,1866,1867],{"class":327,"line":364},[325,1868,1869],{"class":600},"# Item 5: service\n",[325,1871,1872],{"class":327,"line":636},[325,1873,1874],{"class":600},"# Join the remaining items into a string, separated with whitespaces\n",[325,1876,1877],{"class":327,"line":641},[325,1878,1879],{"class":600},"# Print the results after the loop\n",[325,1881,1882],{"class":327,"line":650},[325,1883,346],{"emptyLinePlaceholder":345},[325,1885,1886,1889,1891,1894,1896,1899,1902,1904,1907,1909,1911,1913,1916,1918,1920,1922,1925,1927,1929],{"class":327,"line":954},[325,1887,1888],{"class":606},"results ",[325,1890,611],{"class":610},[325,1892,1893],{"class":606}," {",[325,1895,1812],{"class":335},[325,1897,1898],{"class":606},": ",[325,1900,1901],{"class":335},"''",[325,1903,811],{"class":606},[325,1905,1906],{"class":335},"'host'",[325,1908,1898],{"class":606},[325,1910,1901],{"class":335},[325,1912,811],{"class":606},[325,1914,1915],{"class":335},"'service'",[325,1917,1898],{"class":606},[325,1919,1901],{"class":335},[325,1921,811],{"class":606},[325,1923,1924],{"class":335},"'message'",[325,1926,1898],{"class":606},[325,1928,1901],{"class":335},[325,1930,1931],{"class":606},"}\n",[325,1933,1934],{"class":327,"line":959},[325,1935,346],{"emptyLinePlaceholder":345},[325,1937,1938,1940,1943,1945],{"class":327,"line":1133},[325,1939,1081],{"class":610},[325,1941,1942],{"class":606}," item ",[325,1944,1087],{"class":610},[325,1946,1947],{"class":606}," log_line_split:\n",[325,1949,1950],{"class":327,"line":1138},[325,1951,346],{"emptyLinePlaceholder":345},[325,1953,1954,1956,1959,1961,1964,1967,1970],{"class":327,"line":1143},[325,1955,1382],{"class":610},[325,1957,1958],{"class":606}," results[",[325,1960,1812],{"class":335},[325,1962,1963],{"class":606},"] ",[325,1965,1966],{"class":610},"==",[325,1968,1969],{"class":335}," ''",[325,1971,1325],{"class":606},[325,1973,1975,1978,1980,1982,1984,1987,1990,1993,1995,1997],{"class":327,"line":1974},14,[325,1976,1977],{"class":606},"        results[",[325,1979,1812],{"class":335},[325,1981,1963],{"class":606},[325,1983,611],{"class":610},[325,1985,1986],{"class":335}," ' '",[325,1988,1989],{"class":606},".join(log_line_split[",[325,1991,1992],{"class":352},"0",[325,1994,1815],{"class":606},[325,1996,962],{"class":352},[325,1998,1999],{"class":606},"])\n",[325,2001,2003],{"class":327,"line":2002},15,[325,2004,346],{"emptyLinePlaceholder":345},[325,2006,2008,2011,2013,2015,2017,2019,2021],{"class":327,"line":2007},16,[325,2009,2010],{"class":610},"    elif",[325,2012,1958],{"class":606},[325,2014,1906],{"class":335},[325,2016,1963],{"class":606},[325,2018,1966],{"class":610},[325,2020,1969],{"class":335},[325,2022,1325],{"class":606},[325,2024,2026,2028,2030,2032,2034,2037,2039],{"class":327,"line":2025},17,[325,2027,1977],{"class":606},[325,2029,1906],{"class":335},[325,2031,1963],{"class":606},[325,2033,611],{"class":610},[325,2035,2036],{"class":606}," log_line_split[",[325,2038,962],{"class":352},[325,2040,1253],{"class":606},[325,2042,2044],{"class":327,"line":2043},18,[325,2045,346],{"emptyLinePlaceholder":345},[325,2047,2049,2051,2053,2055,2057,2059,2061],{"class":327,"line":2048},19,[325,2050,2010],{"class":610},[325,2052,1958],{"class":606},[325,2054,1915],{"class":335},[325,2056,1963],{"class":606},[325,2058,1966],{"class":610},[325,2060,1969],{"class":335},[325,2062,1325],{"class":606},[325,2064,2066,2068,2070,2072,2074,2076,2079],{"class":327,"line":2065},20,[325,2067,1977],{"class":606},[325,2069,1915],{"class":335},[325,2071,1963],{"class":606},[325,2073,611],{"class":610},[325,2075,2036],{"class":606},[325,2077,2078],{"class":352},"4",[325,2080,1253],{"class":606},[325,2082,2084],{"class":327,"line":2083},21,[325,2085,346],{"emptyLinePlaceholder":345},[325,2087,2089,2092],{"class":327,"line":2088},22,[325,2090,2091],{"class":610},"    else",[325,2093,1325],{"class":606},[325,2095,2097,2099,2101,2103,2106,2108,2111],{"class":327,"line":2096},23,[325,2098,1977],{"class":606},[325,2100,1924],{"class":335},[325,2102,1963],{"class":606},[325,2104,2105],{"class":610},"+=",[325,2107,1986],{"class":335},[325,2109,2110],{"class":610}," +",[325,2112,2113],{"class":606}," item\n",[325,2115,2117],{"class":327,"line":2116},24,[325,2118,346],{"emptyLinePlaceholder":345},[325,2120,2122,2124],{"class":327,"line":2121},25,[325,2123,644],{"class":352},[325,2125,2126],{"class":606},"(results)\n",[12,2128,2129,2130,2133,2134,2137],{},"O algoritmo sugerido percorre todos os itens da linha de log e aplica a mesma operação para os três primeiros. ",[243,2131,2132],{},"log_line_split[0:3]"," extrai uma fatia com três itens em uma nova lista. Chamar ",[243,2135,2136],{},"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.",[12,2139,2140,2142,2143,2146],{},[221,2141,223],{}," se você precisar encerrar um script antes da hora, use ",[243,2144,2145],{},"sys.exit()",". O restante do código não vai ser executado.",[316,2148,2150],{"className":591,"code":2149,"language":593,"meta":321,"style":321},"import sys\nsys.exit(1)\n",[243,2151,2152,2159],{"__ignoreMap":321},[325,2153,2154,2156],{"class":327,"line":328},[325,2155,1055],{"class":610},[325,2157,2158],{"class":606}," sys\n",[325,2160,2161,2164,2167],{"class":327,"line":342},[325,2162,2163],{"class":606},"sys.exit(",[325,2165,2166],{"class":352},"1",[325,2168,1627],{"class":606},[12,2170,2171],{},"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.",[231,2173,131],{"id":2174},"faça-o-parsing-de-arquivos-de-log-usando-expressões-regulares",[12,2176,2177,2178,2183,2184,2189],{},"Existem diferentes RFCs para o formato syslog: a ",[22,2179,2182],{"href":2180,"rel":2181},"https://datatracker.ietf.org/doc/html/rfc3164",[],"RFC 3164"," está obsoleta, mas ainda aparece na prática como configuração padrão (seguindo o padrão visto acima), enquanto a ",[22,2185,2188],{"href":2186,"rel":2187},"https://datatracker.ietf.org/doc/html/rfc5424",[],"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.",[12,2191,2192,2193,214],{},"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 ",[22,2194,2197],{"href":2195,"rel":2196},"https://regex101.com/",[],"regex101.com",[12,2199,2200,2202],{},[221,2201,223],{}," 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.",[316,2204,2206],{"className":591,"code":2205,"language":593,"meta":321,"style":321},"# Define the syslog log format regex in a dictionary\n# Add entries for RFC3164, RFC5424\nregex_log_pattern = {\n    '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(.*)$',\n    '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(.*)$;'\n}\n",[243,2207,2208,2213,2218,2228,2241,2281],{"__ignoreMap":321},[325,2209,2210],{"class":327,"line":328},[325,2211,2212],{"class":600},"# Define the syslog log format regex in a dictionary\n",[325,2214,2215],{"class":327,"line":342},[325,2216,2217],{"class":600},"# Add entries for RFC3164, RFC5424\n",[325,2219,2220,2223,2225],{"class":327,"line":349},[325,2221,2222],{"class":606},"regex_log_pattern ",[325,2224,611],{"class":610},[325,2226,2227],{"class":606}," {\n",[325,2229,2230,2233,2235,2238],{"class":327,"line":359},[325,2231,2232],{"class":335},"    'rfc3164'",[325,2234,1898],{"class":606},[325,2236,2237],{"class":335},"'([A-Z][a-z][a-z]\\s{1,2}\\d{1,2}\\s\\d{2}[:]\\d{2}[:]\\d{2})\\s([\\w][\\w\\d\\.@-]*)\\s(.*)$'",[325,2239,2240],{"class":606},",\n",[325,2242,2243,2246,2248,2251,2254,2257,2260,2262,2264,2267,2269,2272,2274,2276,2278],{"class":327,"line":364},[325,2244,2245],{"class":335},"    'rfc5424'",[325,2247,1898],{"class":606},[325,2249,2250],{"class":335},"'(?:(\\d",[325,2252,2253],{"class":352},"{4}",[325,2255,2256],{"class":335},"[-]\\d",[325,2258,2259],{"class":352},"{2}",[325,2261,2256],{"class":335},[325,2263,2259],{"class":352},[325,2265,2266],{"class":335},"[T]\\d",[325,2268,2259],{"class":352},[325,2270,2271],{"class":335},"[:]\\d",[325,2273,2259],{"class":352},[325,2275,2271],{"class":335},[325,2277,2259],{"class":352},[325,2279,2280],{"class":335},"(?:\\.\\d{1,6})?(?:[+-]\\d{2}[:]\\d{2}|Z)?)|-)\\s(?:([\\w][\\w\\d\\.@-]*)|-)\\s(.*)$;'\n",[325,2282,2283],{"class":327,"line":636},[325,2284,1931],{"class":606},[12,2286,2287],{},"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.",[316,2289,2291],{"className":591,"code":2290,"language":593,"meta":321,"style":321},"# Create a function that parses a log file\n# Input parameter: file path\n# Match log line against regex_log_pattern\n# Return the results as dictionary list: log line, pattern, extracted columns\n",[243,2292,2293,2298,2303,2308],{"__ignoreMap":321},[325,2294,2295],{"class":327,"line":328},[325,2296,2297],{"class":600},"# Create a function that parses a log file\n",[325,2299,2300],{"class":327,"line":342},[325,2301,2302],{"class":600},"# Input parameter: file path\n",[325,2304,2305],{"class":327,"line":349},[325,2306,2307],{"class":600},"# Match log line against regex_log_pattern\n",[325,2309,2310],{"class":327,"line":359},[325,2311,2312],{"class":600},"# Return the results as dictionary list: log line, pattern, extracted columns\n",[12,2314,2315],{},[478,2316],{"alt":2317,"src":2318,"title":2317},"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","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398602/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_format_regex_function_instructions_01.png",[12,2320,2321,2322,2325],{},"Lembra da indentação para abrir um novo escopo? A mesma regra vale para funções em Python. O identificador ",[243,2323,2324],{},"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.",[12,2327,2328],{},"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.",[12,2330,2331],{},[478,2332],{"alt":2333,"src":2334,"title":2333},"Code suggestion para fazer parsing de todos os arquivos de log e imprimir o conjunto de resultados","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398603/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_format_regex_function_instructions_02.png",[12,2336,2337],{},"Um exemplo completo e funcional pode ficar assim:",[316,2339,2342],{"className":2340,"code":2341,"language":769,"meta":321},[767],"import os\n\n# Specify the path and file suffix in variables\npath = 'log-data/'\nfile_suffix = '.log'\n\n# Read all file paths in the directory into a list\nlog_files = []\nfor root, directories, files in os.walk(path):\n    for file in files:\n        if file.endswith(file_suffix):\n            log_files.append(os.path.join(root, file))\n\n# Define the syslog log format regex in a dictionary\n# Add entries for RFC3164, RFC5424\nregex_log_pattern = {\n    '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(.*)$',\n    '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(.*)$;'\n}\n\n# Create a function that parses a log file\n# Input parameter: file path\n# Match log line against regex_log_pattern\n# Return the results as dictionary list: log line, pattern name, extracted columns\nimport re\n\ndef parse_log_file(file_path):\n    # Read the log file\n    with open(file_path, 'r') as f:\n        log_lines = f.readlines()\n\n    # Create a list to store the results\n    results = []\n\n    # Iterate over the log lines\n    for log_line in log_lines:\n        # Match the log line against the regex pattern\n        for pattern_name, pattern in regex_log_pattern.items():\n            match = re.match(pattern, log_line)\n\n            # If the log line matches the pattern, add the results to the list\n            if match:\n                extracted_columns = match.groups()\n                results.append({\n                    'log_line': log_line,\n                    'pattern_name': pattern_name,\n                    'extracted_columns': extracted_columns,\n                    'source_file': file_path\n                })\n\n    # Return the results\n    return results\n\n# Parse all files and print results\nfor log_file in log_files:\n    results = parse_log_file(log_file)\n    print(results)\n\n",[243,2343,2341],{"__ignoreMap":321},[12,2345,2346,2347,2350],{},"Vamos entender o que a função ",[243,2348,2349],{},"parse_log_file()"," faz:",[301,2352,2353,2359,2365,2368,2371,2377,2380,2396,2399],{},[19,2354,2355,2356,214],{},"Abre o arquivo a partir do parâmetro ",[243,2357,2358],{},"file_path",[19,2360,2361,2362,214],{},"Lê todas as linhas em uma nova variável, ",[243,2363,2364],{},"log_lines",[19,2366,2367],{},"Cria uma lista de resultados para armazenar todos os itens.",[19,2369,2370],{},"Itera sobre as linhas de log.",[19,2372,2373,2374,214],{},"Confere o match contra todos os padrões regex configurados em ",[243,2375,2376],{},"regex_log_pattern",[19,2378,2379],{},"Se encontra um match, extrai os valores das colunas correspondentes.",[19,2381,2382,2383,811,2386,811,2389,2392,2393,214],{},"Cria um item de resultado, incluindo os valores das chaves ",[243,2384,2385],{},"log_line",[243,2387,2388],{},"pattern_name",[243,2390,2391],{},"extracted_colums"," e ",[243,2394,2395],{},"source_file",[19,2397,2398],{},"Adiciona o item de resultado à lista de resultados.",[19,2400,2401],{},"Retorna a lista de resultados.",[12,2403,2404],{},"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.",[231,2406,137],{"id":2407},"formato-de-log-avançado-authlog",[12,2409,2410],{},"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.",[12,2412,2413,2414,2417],{},"O trecho a seguir, extraído do ",[243,2415,2416],{},"/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.",[316,2419,2422],{"className":2420,"code":2421,"language":769,"meta":321},[767],"Oct 15 00:00:19 ebpf-chaos sshd[3967944]: Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2\nOct 15 00:00:20 ebpf-chaos sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2\nOct 15 00:00:21 ebpf-chaos sshd[3967944]: Received disconnect from 93.254.246.194 port 48840:11: Bye Bye [preauth]\nOct 15 00:00:21 ebpf-chaos sshd[3967944]: Disconnected from invalid user ubuntu 93.254.246.194 port 48840 [preauth]\nOct 15 00:00:24 ebpf-chaos sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2\nOct 15 00:00:25 ebpf-chaos sshd[3967916]: Received disconnect from 180.101.88.227 port 44397:11:  [preauth]\nOct 15 00:00:25 ebpf-chaos sshd[3967916]: Disconnected from authenticating user root 180.101.88.227 port 44397 [preauth]\nOct 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\nOct 15 00:00:25 ebpf-chaos sshd[3967998]: Invalid user teamspeak from 185.218.20.10 port 33436\n",[243,2423,2421],{"__ignoreMap":321},[12,2425,2426,2429,2430,2435],{},[221,2427,2428],{},"Dica de prevenção de intrusão:"," configure um firewall e use o ",[22,2431,2434],{"href":2432,"rel":2433},"https://en.wikipedia.org/wiki/Fail2ban",[],"fail2ban"," para bloquear tentativas de login inválidas.",[12,2437,2438,2439,2442],{},"O próximo exercício é estender a lógica para entender as partes de texto livre da mensagem de log — por exemplo, ",[243,2440,2441],{},"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.",[12,2444,2445],{},"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.",[301,2447,2448,2457],{},[19,2449,2450,2451,2392,2454,214],{},"Conte o número de mensagens ",[243,2452,2453],{},"Failed password",[243,2455,2456],{},"Invalid user",[19,2458,2459],{},"Retorne os resultados com a contagem, o arquivo de log e o padrão usado.",[12,2461,2462],{},[478,2463],{"alt":2464,"src":2465,"title":2464},"Code suggestions para um parser de mensagens de log que conta as falhas do auth.log","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398605/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_parse_log_message_auth_log.png",[12,2467,2468],{},"Uma sugestão funcional pode ser este código:",[316,2470,2472],{"className":591,"code":2471,"language":593,"meta":321,"style":321},"# Create a function that parses a log file message from the last extracted_columns entry\n# Input: Parsed log lines results list\n# Loop over all log lines in the list, and extract the last list item as message\n# Count failure strings in the message: Failed password, Invalid user\n# Return the results if failure count greater 0: log_file, count, failure string\ndef parse_log_file_message(results):\n    failure_results = []\n\n    # Iterate over the log lines\n    for result in results:\n        # Extract the message from the last list item\n        message = result['extracted_columns'][-1]\n\n        # Count the number of failure strings in the message\n        failure_count = message.count('Failed password') + message.count('Invalid user')\n\n        # If the failure count is greater than 0, add the results to the list\n        if failure_count > 0:\n            failure_results.append({\n                'log_file': result['source_file'],\n                'count': failure_count,\n                'failure_string': message\n            })\n\n    # Return the results\n    return failure_results\n\n# Parse all files and print results\nfor log_file in log_files:\n    results = parse_log_file(log_file)\n    failure_results = parse_log_file_message(results)\n    print(failure_results)\n\n",[243,2473,2474,2479,2484,2489,2494,2499,2509,2518,2522,2527,2539,2544,2567,2571,2576,2601,2605,2610,2625,2630,2644,2652,2660,2665,2669,2674,2683,2688,2694,2706,2717,2727],{"__ignoreMap":321},[325,2475,2476],{"class":327,"line":328},[325,2477,2478],{"class":600},"# Create a function that parses a log file message from the last extracted_columns entry\n",[325,2480,2481],{"class":327,"line":342},[325,2482,2483],{"class":600},"# Input: Parsed log lines results list\n",[325,2485,2486],{"class":327,"line":349},[325,2487,2488],{"class":600},"# Loop over all log lines in the list, and extract the last list item as message\n",[325,2490,2491],{"class":327,"line":359},[325,2492,2493],{"class":600},"# Count failure strings in the message: Failed password, Invalid user\n",[325,2495,2496],{"class":327,"line":364},[325,2497,2498],{"class":600},"# Return the results if failure count greater 0: log_file, count, failure string\n",[325,2500,2501,2503,2506],{"class":327,"line":636},[325,2502,2324],{"class":610},[325,2504,2505],{"class":331}," parse_log_file_message",[325,2507,2508],{"class":606},"(results):\n",[325,2510,2511,2514,2516],{"class":327,"line":641},[325,2512,2513],{"class":606},"    failure_results ",[325,2515,611],{"class":610},[325,2517,1076],{"class":606},[325,2519,2520],{"class":327,"line":650},[325,2521,346],{"emptyLinePlaceholder":345},[325,2523,2524],{"class":327,"line":954},[325,2525,2526],{"class":600},"    # Iterate over the log lines\n",[325,2528,2529,2531,2534,2536],{"class":327,"line":959},[325,2530,1095],{"class":610},[325,2532,2533],{"class":606}," result ",[325,2535,1087],{"class":610},[325,2537,2538],{"class":606}," results:\n",[325,2540,2541],{"class":327,"line":1133},[325,2542,2543],{"class":600},"        # Extract the message from the last list item\n",[325,2545,2546,2549,2551,2554,2557,2560,2563,2565],{"class":327,"line":1138},[325,2547,2548],{"class":606},"        message ",[325,2550,611],{"class":610},[325,2552,2553],{"class":606}," result[",[325,2555,2556],{"class":335},"'extracted_columns'",[325,2558,2559],{"class":606},"][",[325,2561,2562],{"class":610},"-",[325,2564,2166],{"class":352},[325,2566,1253],{"class":606},[325,2568,2569],{"class":327,"line":1143},[325,2570,346],{"emptyLinePlaceholder":345},[325,2572,2573],{"class":327,"line":1974},[325,2574,2575],{"class":600},"        # Count the number of failure strings in the message\n",[325,2577,2578,2581,2583,2586,2589,2591,2594,2596,2599],{"class":327,"line":2002},[325,2579,2580],{"class":606},"        failure_count ",[325,2582,611],{"class":610},[325,2584,2585],{"class":606}," message.count(",[325,2587,2588],{"class":335},"'Failed password'",[325,2590,817],{"class":606},[325,2592,2593],{"class":610},"+",[325,2595,2585],{"class":606},[325,2597,2598],{"class":335},"'Invalid user'",[325,2600,1627],{"class":606},[325,2602,2603],{"class":327,"line":2007},[325,2604,346],{"emptyLinePlaceholder":345},[325,2606,2607],{"class":327,"line":2025},[325,2608,2609],{"class":600},"        # If the failure count is greater than 0, add the results to the list\n",[325,2611,2612,2614,2617,2620,2623],{"class":327,"line":2043},[325,2613,1110],{"class":610},[325,2615,2616],{"class":606}," failure_count ",[325,2618,2619],{"class":610},">",[325,2621,2622],{"class":352}," 0",[325,2624,1325],{"class":606},[325,2626,2627],{"class":327,"line":2048},[325,2628,2629],{"class":606},"            failure_results.append({\n",[325,2631,2632,2635,2638,2641],{"class":327,"line":2065},[325,2633,2634],{"class":335},"                'log_file'",[325,2636,2637],{"class":606},": result[",[325,2639,2640],{"class":335},"'source_file'",[325,2642,2643],{"class":606},"],\n",[325,2645,2646,2649],{"class":327,"line":2083},[325,2647,2648],{"class":335},"                'count'",[325,2650,2651],{"class":606},": failure_count,\n",[325,2653,2654,2657],{"class":327,"line":2088},[325,2655,2656],{"class":335},"                'failure_string'",[325,2658,2659],{"class":606},": message\n",[325,2661,2662],{"class":327,"line":2096},[325,2663,2664],{"class":606},"            })\n",[325,2666,2667],{"class":327,"line":2116},[325,2668,346],{"emptyLinePlaceholder":345},[325,2670,2671],{"class":327,"line":2121},[325,2672,2673],{"class":600},"    # Return the results\n",[325,2675,2677,2680],{"class":327,"line":2676},26,[325,2678,2679],{"class":610},"    return",[325,2681,2682],{"class":606}," failure_results\n",[325,2684,2686],{"class":327,"line":2685},27,[325,2687,346],{"emptyLinePlaceholder":345},[325,2689,2691],{"class":327,"line":2690},28,[325,2692,2693],{"class":600},"# Parse all files and print results\n",[325,2695,2697,2699,2702,2704],{"class":327,"line":2696},29,[325,2698,1081],{"class":610},[325,2700,2701],{"class":606}," log_file ",[325,2703,1087],{"class":610},[325,2705,1469],{"class":606},[325,2707,2709,2712,2714],{"class":327,"line":2708},30,[325,2710,2711],{"class":606},"    results ",[325,2713,611],{"class":610},[325,2715,2716],{"class":606}," parse_log_file(log_file)\n",[325,2718,2720,2722,2724],{"class":327,"line":2719},31,[325,2721,2513],{"class":606},[325,2723,611],{"class":610},[325,2725,2726],{"class":606}," parse_log_file_message(results)\n",[325,2728,2730,2732],{"class":327,"line":2729},32,[325,2731,828],{"class":352},[325,2733,2734],{"class":606},"(failure_results)\n",[12,2736,2737,2738,2740,2741,2744,2745,2748,2749,2752,2753,811,2756,2392,2759,2762],{},"O algoritmo segue as implementações anteriores: primeiro, cria um array de resultados para armazenar os dados que derem match. Depois, itera sobre as ",[243,2739,2364],{}," já processadas na lista. Cada linha de log contém a chave ",[243,2742,2743],{},"extracted_columns",", que guarda a string de mensagem livre no final. O próximo passo é chamar a função ",[243,2746,2747],{},"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 ",[243,2750,2751],{},"failure_count",". Se o valor for maior que zero, o resultado é adicionado à lista de resultados, incluindo os pares chave-valor ",[243,2754,2755],{},"log_file",[243,2757,2758],{},"count",[243,2760,2761],{},"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.",[12,2764,2765],{},"Execute o script para inspecionar os matches detectados. Vale notar que a estrutura de dados ainda pode ser otimizada em passos futuros de aprendizado.",[316,2767,2769],{"className":318,"code":2768,"language":320,"meta":321,"style":321},"python3 log_reader.py\n\n[{'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'}]\n",[243,2770,2771,2777,2781],{"__ignoreMap":321},[325,2772,2773,2775],{"class":327,"line":328},[325,2774,668],{"class":331},[325,2776,671],{"class":335},[325,2778,2779],{"class":327,"line":342},[325,2780,346],{"emptyLinePlaceholder":345},[325,2782,2783,2786,2789,2791,2793,2795,2798,2801,2804,2806,2809,2812,2814,2816,2818,2820,2822,2824,2826,2828,2831,2833,2835,2837,2839,2841,2843,2845,2847,2849,2851,2853,2855,2857,2859,2861,2863,2865,2867,2869,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2893,2895,2897,2899,2901,2903,2905,2907,2909,2911,2914],{"class":327,"line":349},[325,2784,2785],{"class":606},"[{",[325,2787,2788],{"class":335},"'log_file'",[325,2790,1898],{"class":606},[325,2792,1250],{"class":335},[325,2794,811],{"class":606},[325,2796,2797],{"class":335},"'count'",[325,2799,2800],{"class":606},": 1, ",[325,2802,2803],{"class":335},"'failure_string'",[325,2805,1898],{"class":606},[325,2807,2808],{"class":335},"'sshd[3967944]: Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2'",[325,2810,2811],{"class":606},"}, {",[325,2813,2788],{"class":335},[325,2815,1898],{"class":606},[325,2817,1250],{"class":335},[325,2819,811],{"class":606},[325,2821,2797],{"class":335},[325,2823,2800],{"class":606},[325,2825,2803],{"class":335},[325,2827,1898],{"class":606},[325,2829,2830],{"class":335},"'sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2'",[325,2832,2811],{"class":606},[325,2834,2788],{"class":335},[325,2836,1898],{"class":606},[325,2838,1250],{"class":335},[325,2840,811],{"class":606},[325,2842,2797],{"class":335},[325,2844,2800],{"class":606},[325,2846,2803],{"class":335},[325,2848,1898],{"class":606},[325,2850,2830],{"class":335},[325,2852,2811],{"class":606},[325,2854,2788],{"class":335},[325,2856,1898],{"class":606},[325,2858,1250],{"class":335},[325,2860,811],{"class":606},[325,2862,2797],{"class":335},[325,2864,2800],{"class":606},[325,2866,2803],{"class":335},[325,2868,1898],{"class":606},[325,2870,2871],{"class":335},"'sshd[3967998]: Invalid user teamspeak from 185.218.20.10 port 33436'",[325,2873,2811],{"class":606},[325,2875,2788],{"class":335},[325,2877,1898],{"class":606},[325,2879,1250],{"class":335},[325,2881,811],{"class":606},[325,2883,2797],{"class":335},[325,2885,2800],{"class":606},[325,2887,2803],{"class":335},[325,2889,1898],{"class":606},[325,2891,2892],{"class":335},"'sshd[3967998]: Failed password for invalid user teamspeak from 185.218.20.10 port 33436 ssh2'",[325,2894,2811],{"class":606},[325,2896,2788],{"class":335},[325,2898,1898],{"class":606},[325,2900,1250],{"class":335},[325,2902,811],{"class":606},[325,2904,2797],{"class":335},[325,2906,2800],{"class":606},[325,2908,2803],{"class":335},[325,2910,1898],{"class":606},[325,2912,2913],{"class":335},"'sshd[3968077]: Invalid user mcserver from 218.211.33.146 port 50950'",[325,2915,2916],{"class":606},"}]\n",[231,2918,143],{"id":2919},"fazendo-parsing-de-mais-tipos-logging-estruturado",[12,2921,2922],{},"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:",[316,2924,2927],{"className":2925,"code":2926,"language":769,"meta":321},[767],"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\nsg=\"write block\" mint=1697558404661 maxt=1697565600000 ulid=01HCZG4ZX51GTH8H7PVBYDF4N6 duration=148.675854ms\nOct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.464Z caller=head.go:1213 level=info component=tsdb msg\n=\"Head GC completed\" caller=truncateMemory duration=6.845245ms\nOct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.467Z caller=checkpoint.go:100 level=info component=tsd\nb msg=\"Creating checkpoint\" from_segment=2308 to_segment=2309 mint=1697565600000\nOct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.517Z caller=head.go:1185 level=info component=tsdb msg\n=\"WAL checkpoint complete\" first=2308 last=2309 duration=50.052621ms\n",[243,2928,2926],{"__ignoreMap":321},[12,2930,2931,2932,2934],{},"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 ",[243,2933,611],{},". 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.",[12,2936,2937,2938,2941],{},"Pratique seguindo o exemplo anterior para fazer o parsing do formato ",[243,2939,2940],{},"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:",[316,2943,2945],{"className":591,"code":2944,"language":593,"meta":321,"style":321},"# Create a function that parses a log file message from the last extracted_columns entry\n# Input: Parsed log lines results list\n# Loop over all log lines in the list, and extract the last list item as message\n# Parse structured logging key-value pairs into a dictionary\n# Return results: log_file, dictionary\n",[243,2946,2947,2951,2955,2959,2964],{"__ignoreMap":321},[325,2948,2949],{"class":327,"line":328},[325,2950,2478],{"class":600},[325,2952,2953],{"class":327,"line":342},[325,2954,2483],{"class":600},[325,2956,2957],{"class":327,"line":349},[325,2958,2488],{"class":600},[325,2960,2961],{"class":327,"line":359},[325,2962,2963],{"class":600},"# Parse structured logging key-value pairs into a dictionary\n",[325,2965,2966],{"class":327,"line":364},[325,2967,2968],{"class":600},"# Return results: log_file, dictionary\n",[12,2970,2971],{},[478,2972],{"alt":2973,"src":2974,"title":2973},"Code suggestions para fazer parsing do formato de logging estruturado na parte de mensagem do arquivo de log","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398606/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_propose_structured_logging_message_parser.png",[231,2976,149],{"id":2977},"imprimindo-e-formatando-os-resultados",[12,2979,2980,2981,2983],{},"Muitos dos exemplos usaram a instrução ",[243,2982,854],{}," 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.).",[12,2985,2986,2987,2989,2990,214],{},"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 ",[243,2988,1055],{}," aqui para trazer bibliotecas para o escopo atual e acessar seus métodos — por exemplo, ",[243,2991,2992],{},"json.dumps",[316,2994,2996],{"className":591,"code":2995,"language":593,"meta":321,"style":321},"import json\nprint(json.dumps(structured_results, indent=4))\n",[243,2997,2998,3005],{"__ignoreMap":321},[325,2999,3000,3002],{"class":327,"line":328},[325,3001,1055],{"class":610},[325,3003,3004],{"class":606}," json\n",[325,3006,3007,3009,3012,3015,3017,3019],{"class":327,"line":342},[325,3008,644],{"class":352},[325,3010,3011],{"class":606},"(json.dumps(structured_results, ",[325,3013,3014],{"class":1098},"indent",[325,3016,611],{"class":610},[325,3018,2078],{"class":352},[325,3020,1126],{"class":606},[12,3022,3023],{},[478,3024],{"alt":559,"src":560,"title":559},[12,3026,3027],{},"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.",[316,3029,3031],{"className":591,"code":3030,"language":593,"meta":321,"style":321},"# Create a pretty print function with indent 4\n",[243,3032,3033],{"__ignoreMap":321},[325,3034,3035],{"class":327,"line":328},[325,3036,3030],{"class":600},[12,3038,3039],{},[478,3040],{"alt":3041,"src":3042,"title":3041},"Code suggestions para uma função de pretty-print","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398606/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_pretty_print.png",[12,3044,3045],{},"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.",[191,3047,155],{"id":3048},"gerenciamento-de-dependências-e-verificação-contínua",[231,3050,163],{"id":3051},"pip-e-pyenv-trazendo-estrutura-para-o-python",[12,3053,3054,3055,3063,3064,3066],{},"As dependências podem ser gerenciadas no ",[22,3056,3059,3060],{"href":3057,"rel":3058},"https://pip.pypa.io/en/stable/reference/requirements-file-format/",[],"arquivo ",[243,3061,3062],{},"requirements.txt",", incluindo dependências de versão opcionais. Usar o arquivo ",[243,3065,3062],{}," 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:",[316,3068,3070],{"className":318,"code":3069,"language":320,"meta":321,"style":321},"pip install -r requirements.txt\n",[243,3071,3072],{"__ignoreMap":321},[325,3073,3074,3077,3079,3082],{"class":327,"line":328},[325,3075,3076],{"class":331},"pip",[325,3078,387],{"class":335},[325,3080,3081],{"class":352}," -r",[325,3083,3084],{"class":335}," requirements.txt\n",[12,3086,3087,3088,214],{},"Algumas distribuições Linux não instalam o gerenciador de pacotes pip por padrão — no Ubuntu/Debian, por exemplo, é preciso instalar o pacote ",[243,3089,3090],{},"python3-pip",[12,3092,3093,3094,3099],{},"Você pode gerenciar diferentes ambientes virtuais usando o ",[22,3095,3098],{"href":3096,"rel":3097},"https://docs.python.org/3/library/venv.html",[],"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.",[316,3101,3103],{"className":318,"code":3102,"language":320,"meta":321,"style":321},"pip install virtualenv\nvirtualenv venv\nsource venv/bin/activate\n",[243,3104,3105,3114,3122],{"__ignoreMap":321},[325,3106,3107,3109,3111],{"class":327,"line":328},[325,3108,3076],{"class":331},[325,3110,387],{"class":335},[325,3112,3113],{"class":335}," virtualenv\n",[325,3115,3116,3119],{"class":327,"line":342},[325,3117,3118],{"class":331},"virtualenv",[325,3120,3121],{"class":335}," venv\n",[325,3123,3124,3127],{"class":327,"line":349},[325,3125,3126],{"class":352},"source",[325,3128,3129],{"class":335}," venv/bin/activate\n",[231,3131,169],{"id":3132},"automação-configure-o-pipeline-de-cicd-para-python",[12,3134,3135,3136,3141],{},"O ",[22,3137,3140],{"href":3138,"rel":3139},"https://docs.gitlab.com/ci/",[],"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:",[301,3143,3144,3159,3168],{},[19,3145,3146,3147,3152,3153,3158],{},"Faça lint do código-fonte e verifique erros de formatação. O exemplo usa o ",[22,3148,3151],{"href":3149,"rel":3150},"https://pypi.org/project/pyflakes/",[],"Pyflakes",", um linter maduro, e o ",[22,3154,3157],{"href":3155,"rel":3156},"https://docs.astral.sh/ruff/",[],"Ruff",", um linter rápido escrito em Rust.",[19,3160,3161,3162,3167],{},"Faça cache das dependências instaladas pelo gerenciador de pacotes pip, seguindo a documentação de ",[22,3163,3166],{"href":3164,"rel":3165},"https://docs.gitlab.com/ci/caching/#cache-python-dependencies",[],"cache do Python no GitLab CI/CD",". Isso economiza tempo e recursos nas execuções repetidas do pipeline de CI/CD.",[19,3169,3170],{},"Use builds em matriz paralela para testar diferentes versões do Python, com base nas imagens de container disponíveis no Docker Hub e suas tags.",[316,3172,3176],{"className":3173,"code":3174,"language":3175,"meta":321,"style":321},"language-yaml shiki shiki-themes github-light","stages:\n  - lint\n  - test\n\ndefault:\n  image: python:latest\n  cache:                      # Pip's cache doesn't store the python packages\n    paths:                    # https://pip.pypa.io/en/stable/topics/caching/\n      - .cache/pip\n  before_script:\n    - python -V               # Print out python version for debugging\n    - pip install virtualenv\n    - virtualenv venv\n    - source venv/bin/activate\n\nvariables:  # Change pip's cache directory to be inside the project directory since we can only cache local items.\n  PIP_CACHE_DIR: \"$CI_PROJECT_DIR/.cache/pip\"\n\n# lint template\n.lint-tmpl:\n  script:\n    - echo \"Linting Python version $VERSION\"\n  parallel:\n    matrix:\n      - VERSION: ['3.9', '3.10', '3.11', '3.12']   # https://hub.docker.com/_/python\n\n# Lint, using Pyflakes: https://pypi.org/project/pyflakes/\nlint-pyflakes:\n  extends: [.lint-tmpl]\n  script:\n    - pip install -r requirements.txt\n    - find . -not -path './venv' -type f -name '*.py' -exec sh -c 'pyflakes {}' \\;\n\n# Lint, using Ruff (Rust): https://docs.astral.sh/ruff/\nlint-ruff:\n  extends: [.lint-tmpl]\n  script:\n    - pip install -r requirements.txt\n    - ruff .\n\n","yaml",[243,3177,3178,3186,3194,3201,3205,3212,3222,3233,3244,3252,3259,3270,3277,3284,3291,3295,3306,3316,3320,3325,3332,3339,3346,3353,3360,3394,3398,3403,3410,3421,3427,3433,3440,3445,3451,3459,3470,3477,3484],{"__ignoreMap":321},[325,3179,3180,3184],{"class":327,"line":328},[325,3181,3183],{"class":3182},"shJU0","stages",[325,3185,1325],{"class":606},[325,3187,3188,3191],{"class":327,"line":342},[325,3189,3190],{"class":606},"  - ",[325,3192,3193],{"class":335},"lint\n",[325,3195,3196,3198],{"class":327,"line":349},[325,3197,3190],{"class":606},[325,3199,3200],{"class":335},"test\n",[325,3202,3203],{"class":327,"line":359},[325,3204,346],{"emptyLinePlaceholder":345},[325,3206,3207,3210],{"class":327,"line":364},[325,3208,3209],{"class":3182},"default",[325,3211,1325],{"class":606},[325,3213,3214,3217,3219],{"class":327,"line":636},[325,3215,3216],{"class":3182},"  image",[325,3218,1898],{"class":606},[325,3220,3221],{"class":335},"python:latest\n",[325,3223,3224,3227,3230],{"class":327,"line":641},[325,3225,3226],{"class":3182},"  cache",[325,3228,3229],{"class":606},":                      ",[325,3231,3232],{"class":600},"# Pip's cache doesn't store the python packages\n",[325,3234,3235,3238,3241],{"class":327,"line":650},[325,3236,3237],{"class":3182},"    paths",[325,3239,3240],{"class":606},":                    ",[325,3242,3243],{"class":600},"# https://pip.pypa.io/en/stable/topics/caching/\n",[325,3245,3246,3249],{"class":327,"line":954},[325,3247,3248],{"class":606},"      - ",[325,3250,3251],{"class":335},".cache/pip\n",[325,3253,3254,3257],{"class":327,"line":959},[325,3255,3256],{"class":3182},"  before_script",[325,3258,1325],{"class":606},[325,3260,3261,3264,3267],{"class":327,"line":1133},[325,3262,3263],{"class":606},"    - ",[325,3265,3266],{"class":335},"python -V",[325,3268,3269],{"class":600},"               # Print out python version for debugging\n",[325,3271,3272,3274],{"class":327,"line":1138},[325,3273,3263],{"class":606},[325,3275,3276],{"class":335},"pip install virtualenv\n",[325,3278,3279,3281],{"class":327,"line":1143},[325,3280,3263],{"class":606},[325,3282,3283],{"class":335},"virtualenv venv\n",[325,3285,3286,3288],{"class":327,"line":1974},[325,3287,3263],{"class":606},[325,3289,3290],{"class":335},"source venv/bin/activate\n",[325,3292,3293],{"class":327,"line":2002},[325,3294,346],{"emptyLinePlaceholder":345},[325,3296,3297,3300,3303],{"class":327,"line":2007},[325,3298,3299],{"class":3182},"variables",[325,3301,3302],{"class":606},":  ",[325,3304,3305],{"class":600},"# Change pip's cache directory to be inside the project directory since we can only cache local items.\n",[325,3307,3308,3311,3313],{"class":327,"line":2025},[325,3309,3310],{"class":3182},"  PIP_CACHE_DIR",[325,3312,1898],{"class":606},[325,3314,3315],{"class":335},"\"$CI_PROJECT_DIR/.cache/pip\"\n",[325,3317,3318],{"class":327,"line":2043},[325,3319,346],{"emptyLinePlaceholder":345},[325,3321,3322],{"class":327,"line":2048},[325,3323,3324],{"class":600},"# lint template\n",[325,3326,3327,3330],{"class":327,"line":2065},[325,3328,3329],{"class":3182},".lint-tmpl",[325,3331,1325],{"class":606},[325,3333,3334,3337],{"class":327,"line":2083},[325,3335,3336],{"class":3182},"  script",[325,3338,1325],{"class":606},[325,3340,3341,3343],{"class":327,"line":2088},[325,3342,3263],{"class":606},[325,3344,3345],{"class":335},"echo \"Linting Python version $VERSION\"\n",[325,3347,3348,3351],{"class":327,"line":2096},[325,3349,3350],{"class":3182},"  parallel",[325,3352,1325],{"class":606},[325,3354,3355,3358],{"class":327,"line":2116},[325,3356,3357],{"class":3182},"    matrix",[325,3359,1325],{"class":606},[325,3361,3362,3364,3367,3370,3373,3375,3378,3380,3383,3385,3388,3391],{"class":327,"line":2121},[325,3363,3248],{"class":606},[325,3365,3366],{"class":3182},"VERSION",[325,3368,3369],{"class":606},": [",[325,3371,3372],{"class":335},"'3.9'",[325,3374,811],{"class":606},[325,3376,3377],{"class":335},"'3.10'",[325,3379,811],{"class":606},[325,3381,3382],{"class":335},"'3.11'",[325,3384,811],{"class":606},[325,3386,3387],{"class":335},"'3.12'",[325,3389,3390],{"class":606},"]   ",[325,3392,3393],{"class":600},"# https://hub.docker.com/_/python\n",[325,3395,3396],{"class":327,"line":2676},[325,3397,346],{"emptyLinePlaceholder":345},[325,3399,3400],{"class":327,"line":2685},[325,3401,3402],{"class":600},"# Lint, using Pyflakes: https://pypi.org/project/pyflakes/\n",[325,3404,3405,3408],{"class":327,"line":2690},[325,3406,3407],{"class":3182},"lint-pyflakes",[325,3409,1325],{"class":606},[325,3411,3412,3415,3417,3419],{"class":327,"line":2696},[325,3413,3414],{"class":3182},"  extends",[325,3416,3369],{"class":606},[325,3418,3329],{"class":335},[325,3420,1253],{"class":606},[325,3422,3423,3425],{"class":327,"line":2708},[325,3424,3336],{"class":3182},[325,3426,1325],{"class":606},[325,3428,3429,3431],{"class":327,"line":2719},[325,3430,3263],{"class":606},[325,3432,3069],{"class":335},[325,3434,3435,3437],{"class":327,"line":2729},[325,3436,3263],{"class":606},[325,3438,3439],{"class":335},"find . -not -path './venv' -type f -name '*.py' -exec sh -c 'pyflakes {}' \\;\n",[325,3441,3443],{"class":327,"line":3442},33,[325,3444,346],{"emptyLinePlaceholder":345},[325,3446,3448],{"class":327,"line":3447},34,[325,3449,3450],{"class":600},"# Lint, using Ruff (Rust): https://docs.astral.sh/ruff/\n",[325,3452,3454,3457],{"class":327,"line":3453},35,[325,3455,3456],{"class":3182},"lint-ruff",[325,3458,1325],{"class":606},[325,3460,3462,3464,3466,3468],{"class":327,"line":3461},36,[325,3463,3414],{"class":3182},[325,3465,3369],{"class":606},[325,3467,3329],{"class":335},[325,3469,1253],{"class":606},[325,3471,3473,3475],{"class":327,"line":3472},37,[325,3474,3336],{"class":3182},[325,3476,1325],{"class":606},[325,3478,3480,3482],{"class":327,"line":3479},38,[325,3481,3263],{"class":606},[325,3483,3069],{"class":335},[325,3485,3487,3489],{"class":327,"line":3486},39,[325,3488,3263],{"class":606},[325,3490,3491],{"class":335},"ruff .\n",[12,3493,3494],{},[478,3495],{"alt":3496,"src":3497,"title":3496},"Visualização do job de lint do Python no GitLab CI/CD, parte dos builds em matriz","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398599/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/gitlab_cicd_python_lint_job_log_view.png",[191,3499,175],{"id":3500},"próximos-passos",[12,3502,3503,3504,3508],{},"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 ",[22,3505,3507],{"href":2195,"rel":3506},[],"regex101",", e o GitLab Duo já sabia o que eu ia escrever.",[12,3510,3511],{},[478,3512],{"alt":3513,"src":3514,"title":3513},"Escrevendo o post do blog do GitLab no VS Code com o apoio do GitLab Duo Code Suggestions","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398600/blog/Content%20Images/learn-python-with-ai-code-suggestions-getting-started/gitlab_duo_code_suggestions_helping_write_the_learning_python_ai_blog_post.png",[12,3516,3517],{},"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.",[231,3519,183],{"id":3520},"exercícios-assíncronos-de-aprendizado",[16,3522,3523,3529,3532,3535,3538],{},[19,3524,3525,3526,214],{},"Implemente a verificação que ainda falta para a variável ",[243,3527,3528],{},"log_file_limit",[19,3530,3531],{},"Imprima um resumo dos resultados em Markdown, além do formato JSON.",[19,3533,3534],{},"Estenda o script para aceitar um filtro de busca como variável de ambiente. Imprima/conte apenas os resultados filtrados.",[19,3536,3537],{},"Estenda o script para aceitar um intervalo de datas. Isso pode exigir converter a coluna de data e hora em um objeto de tempo para comparar o intervalo.",[19,3539,3540],{},"Inspecione o log de um job de pipeline de CI/CD do GitLab e baixe o formato bruto. Estenda o parser de logs para reconhecer esse formato específico e imprimir um resumo.",[231,3542,189],{"id":3543},"compartilhe-seu-feedback",[12,3545,3546,3547,3551],{},"Qual linguagem de programação você está aprendendo ou pensando em aprender? Abra um novo tópico no nosso fórum da ",[22,3548,3550],{"href":3549},"/pt-br/community/","comunidade"," ou no Discord e compartilhe sua experiência.",[12,3553,3554,3555,3559,3560,214],{},"Se você usa o ",[22,3556,3558],{"href":3557},"/pt-br/gitlab-duo-agent-platform/","GitLab Duo"," Code Suggestions, compartilhe suas opiniões e feedback ",[22,3561,3564],{"href":3562,"rel":3563},"https://gitlab.com/gitlab-org/gitlab/-/issues/405152",[],"neste issue de feedback",[3566,3567,3568],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .surfw, html code.shiki .surfw{--shiki-default:#005CC5;--shiki-default-font-weight:bold}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}",{"title":321,"searchDepth":342,"depth":342,"links":3570},[3571,3575,3579,3583,3584,3588,3596,3600],{"id":193,"depth":342,"text":25,"children":3572},[3573,3574],{"id":233,"depth":349,"text":33},{"id":264,"depth":349,"text":39},{"id":285,"depth":342,"text":45,"children":3576},[3577,3578],{"id":299,"depth":349,"text":53},{"id":410,"depth":349,"text":59},{"id":551,"depth":342,"text":65,"children":3580},[3581,3582],{"id":573,"depth":349,"text":73},{"id":687,"depth":349,"text":79},{"id":745,"depth":342,"text":85},{"id":870,"depth":342,"text":91,"children":3585},[3586,3587],{"id":974,"depth":349,"text":99},{"id":1182,"depth":349,"text":105},{"id":1559,"depth":342,"text":111,"children":3589},[3590,3591,3592,3593,3594,3595],{"id":1575,"depth":349,"text":119},{"id":1707,"depth":349,"text":125},{"id":2174,"depth":349,"text":131},{"id":2407,"depth":349,"text":137},{"id":2919,"depth":349,"text":143},{"id":2977,"depth":349,"text":149},{"id":3048,"depth":342,"text":155,"children":3597},[3598,3599],{"id":3051,"depth":349,"text":163},{"id":3132,"depth":349,"text":169},{"id":3500,"depth":342,"text":175,"children":3601},[3602,3603],{"id":3520,"depth":349,"text":183},{"id":3543,"depth":349,"text":189},"ai","2023-11-09","Aprenda a programar em Python com este tutorial guiado junto com o GitLab Duo Code Suggestions e saiba uma nova linguagem de programação.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663918/Blog/Hero%20Images/aipower.jpg",{},"/pt-br/blog/learning-python-with-a-little-help-from-ai-code-suggestions",{"title":5,"description":3606,"ogTitle":5,"ogDescription":3606,"noIndex":3609,"ogImage":3610,"ogUrl":3614,"ogSiteName":3615,"ogType":3616,"canonicalUrls":3614},"https://about.gitlab.com/pt-br/blog/learning-python-with-a-little-help-from-ai-code-suggestions","https://about.gitlab.com","article","learning-python-with-a-little-help-from-ai-code-suggestions","pt-br/blog/learning-python-with-a-little-help-from-ai-code-suggestions",[3620,3621,3622,3623],"DevSecOps platform","tutorial","workflow","AI","BlogPost","f8RVjHjWtaDOgsHTkrUVfyflwOrVhlor5lulYsqtKUo",{"logo":3627,"freeTrial":3632,"sales":3637,"login":3642,"items":3647,"search":3967,"minimal":4002,"duo":4021,"switchNav":4030,"pricingDeployment":4041},{"config":3628},{"href":3629,"dataGaName":3630,"dataGaLocation":3631},"/pt-br/","gitlab logo","header",{"text":3633,"config":3634},"Obtenha uma avaliação gratuita",{"href":3635,"dataGaName":3636,"dataGaLocation":3631},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/pt-br&glm_content=default-saas-trial/","free trial",{"text":3638,"config":3639},"Solicite uma demonstração",{"href":3640,"dataGaName":3641,"dataGaLocation":3631},"/pt-br/sales/?contact-topic=request-demo","sales",{"text":3643,"config":3644},"Fazer login",{"href":3645,"dataGaName":3646,"dataGaLocation":3631},"https://gitlab.com/users/sign_in/","sign in",[3648,3723,3811,3816,3891,3945],{"text":3649,"left":345,"config":3650,"menu":3652},"Plataforma",{"dataNavLevelOne":3651},"platform",{"type":3653,"link":3654,"columns":3658,"feature":3712},"lists",{"text":3655,"config":3656},"Conheça a plataforma",{"href":3657,"dataGaName":3651,"dataGaLocation":3631},"/pt-br/platform/",[3659,3677,3695],{"title":3660,"items":3661},"Execução e fluxos de trabalho",[3662,3667,3672],{"text":3663,"config":3664},"CI/CD",{"href":3665,"dataGaLocation":3631,"dataGaName":3666},"/pt-br/solutions/continuous-integration/","ci/cd",{"text":3668,"config":3669},"Gerenciamento de código-fonte",{"href":3670,"dataGaLocation":3631,"dataGaName":3671},"/pt-br/solutions/source-code-management/","source code management",{"text":3673,"config":3674},"Entrega ágil",{"href":3675,"dataGaLocation":3631,"dataGaName":3676},"/pt-br/solutions/agile-delivery/","agile delivery",{"title":3678,"items":3679},"Segurança e governança",[3680,3685,3690],{"text":3681,"config":3682},"Testes de segurança de aplicações",{"href":3683,"dataGaLocation":3631,"dataGaName":3684},"/pt-br/solutions/application-security-testing/","application security testing",{"text":3686,"config":3687},"Governança e conformidade",{"href":3688,"dataGaLocation":3631,"dataGaName":3689},"/pt-br/solutions/software-compliance/","governance and compliance",{"text":3691,"config":3692},"Segurança da cadeia de suprimentos",{"href":3693,"dataGaLocation":3631,"dataGaName":3694},"/pt-br/solutions/supply-chain/","supply chain security",{"title":3696,"items":3697},"Contexto e IA",[3698,3702,3707],{"text":3699,"config":3700},"Orquestração agêntica",{"href":3557,"dataGaLocation":3631,"dataGaName":3701},"agentic orchestration",{"text":3703,"config":3704},"Grafo de contexto",{"href":3705,"dataGaLocation":3631,"dataGaName":3706},"/pt-br/gitlab-orbit/","context graph",{"text":3708,"config":3709},"Visibilidade e métricas",{"href":3710,"dataGaLocation":3631,"dataGaName":3711},"/pt-br/solutions/visibility-measurement/","visibility and measurement",{"config":3713,"title":3716,"text":3717,"link":3718},{"background":3714,"textColor":3715},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","Por que escolher o GitLab?","Uma plataforma única que oferece velocidade e controle em todo o ciclo de vida do seu software.",{"text":3719,"config":3720},"Saiba mais",{"href":3721,"dataGaName":3722,"dataGaLocation":3631},"/pt-br/why-gitlab/","why gitlab",{"text":3724,"left":345,"config":3725,"menu":3727},"Soluções",{"dataNavLevelOne":3726},"solutions",{"type":3653,"link":3728,"columns":3732,"feature":3802},{"text":3729,"config":3730},"Confira todas as soluções",{"href":3731,"dataGaName":3726,"dataGaLocation":3631},"/pt-br/solutions/",[3733,3751,3769],{"title":3734,"items":3735},"Resultados",[3736,3741,3746],{"text":3737,"config":3738},"Modernização de DevOps",{"href":3739,"dataGaLocation":3631,"dataGaName":3740},"/pt-br/assessments/devops-modernization-assessment/","devops modernization",{"text":3742,"config":3743},"Modernização da segurança",{"href":3744,"dataGaLocation":3631,"dataGaName":3745},"/pt-br/assessments/security-modernization-assessment/","security modernization",{"text":3747,"config":3748},"Modernização da IA",{"href":3749,"dataGaLocation":3631,"dataGaName":3750},"/pt-br/assessments/ai-modernization-assessment/","ai modernization",{"title":3752,"items":3753},"Por tamanho",[3754,3759,3764],{"text":3755,"config":3756},"Enterprise",{"href":3757,"dataGaLocation":3631,"dataGaName":3758},"/pt-br/enterprise/","enterprise",{"text":3760,"config":3761},"Pequenas empresas",{"href":3762,"dataGaLocation":3631,"dataGaName":3763},"/pt-br/small-business/","small business",{"text":3765,"config":3766},"Start-ups",{"href":3767,"dataGaLocation":3631,"dataGaName":3768},"/pt-br/solutions/startups/","startups",{"title":3770,"items":3771},"Indústrias",[3772,3777,3782,3787,3792,3797],{"text":3773,"config":3774},"Serviços financeiros",{"href":3775,"dataGaLocation":3631,"dataGaName":3776},"/pt-br/solutions/finance/","financial services",{"text":3778,"config":3779},"Setor público",{"href":3780,"dataGaLocation":3631,"dataGaName":3781},"/pt-br/solutions/public-sector/","public sector",{"text":3783,"config":3784},"Telecomunicações",{"href":3785,"dataGaLocation":3631,"dataGaName":3786},"/pt-br/solutions/telecommunications/","telecommunications",{"text":3788,"config":3789},"Serviços automotivos",{"href":3790,"dataGaLocation":3631,"dataGaName":3791},"/pt-br/solutions/automotive/","automotive",{"text":3793,"config":3794},"Educação",{"href":3795,"dataGaLocation":3631,"dataGaName":3796},"/pt-br/solutions/education/","education",{"text":3798,"config":3799},"Setor aeroespacial",{"href":3800,"dataGaLocation":3631,"dataGaName":3801},"/pt-br/solutions/aerospace/","aerospace",{"config":3803,"title":3804,"text":3805,"link":3806},{"background":3714,"textColor":3715},"GitLab Transcend","Confira nossas inovações mais recentes, anunciadas no último Transcend.",{"text":3807,"config":3808},"Acesse o blog",{"href":3809,"dataGaName":3810,"dataGaLocation":3631},"/blog/gitlab-transcend-announcements/","gitlab transcend",{"text":3812,"config":3813},"Preços",{"href":3814,"dataGaName":3815,"dataGaLocation":3631,"dataNavLevelOne":3815},"/pt-br/pricing/","pricing",{"text":3817,"config":3818,"menu":3820},"Recursos",{"dataNavLevelOne":3819},"resources",{"type":3653,"link":3821,"columns":3825,"feature":3882},{"text":3822,"config":3823},"Confira todos os recursos",{"href":3824,"dataGaName":3819,"dataGaLocation":3631},"/pt-br/resources/",[3826,3854],{"title":3827,"items":3828},"Descubra",[3829,3834,3839,3844,3849],{"text":3830,"config":3831},"Documentação",{"href":3832,"dataGaName":3833,"dataGaLocation":3631},"https://docs.gitlab.com/","docs",{"text":3835,"config":3836},"University",{"href":3837,"dataGaName":3838,"dataGaLocation":3631},"https://university.gitlab.com/","university",{"text":3840,"config":3841},"Demo Series",{"href":3842,"dataGaName":3843,"dataGaLocation":3631},"/pt-br/gitlab-technical-demo-series/","demo series",{"text":3845,"config":3846},"Demo Hub",{"href":3847,"dataGaName":3848,"dataGaLocation":3631},"/pt-br/demo-hub/","demo hub",{"text":3850,"config":3851},"Serviços",{"href":3852,"dataGaName":3853,"dataGaLocation":3631},"/pt-br/services/","services",{"title":3855,"items":3856},"Conecte-se",[3857,3862,3867,3872,3877],{"text":3858,"config":3859},"Blog",{"href":3860,"dataGaName":3861,"dataGaLocation":3631},"/blog/","blog",{"text":3863,"config":3864},"Comunidade",{"href":3865,"dataGaName":3866,"dataGaLocation":3631},"/community/","community",{"text":3868,"config":3869},"Clientes",{"href":3870,"dataGaName":3871,"dataGaLocation":3631},"/pt-br/customers/","customers",{"text":3873,"config":3874},"Parceiros",{"href":3875,"dataGaName":3876,"dataGaLocation":3631},"/pt-br/partners/","partners",{"text":3878,"config":3879},"Eventos",{"href":3880,"dataGaName":3881,"dataGaLocation":3631},"/events/","events",{"config":3883,"title":3884,"text":3885,"link":3886},{"background":3714,"textColor":3715},"Novidades no GitLab","Acompanhe as melhorias e os recursos mais recentes.",{"text":3887,"config":3888},"Confira as novidades",{"href":3889,"dataGaName":3890,"dataGaLocation":3631},"/pt-br/whats-new/","whats new",{"text":3892,"config":3893,"menu":3895},"Empresa",{"dataNavLevelOne":3894},"company",{"type":3653,"columns":3896},[3897],{"items":3898},[3899,3904,3910,3915,3920,3925,3930,3935,3940],{"text":3900,"config":3901},"Sobre",{"href":3902,"dataGaName":3903,"dataGaLocation":3631},"/pt-br/company/","about",{"text":3905,"config":3906,"footerGa":3909},"Vagas",{"href":3907,"dataGaName":3908,"dataGaLocation":3631},"/jobs/","jobs",{"dataGaName":3908},{"text":3911,"config":3912},"Imprensa",{"href":3913,"dataGaName":3914,"dataGaLocation":3631},"/press/","press",{"text":3916,"config":3917},"Manual",{"href":3918,"dataGaName":3919,"dataGaLocation":3631},"https://handbook.gitlab.com/","handbook",{"text":3921,"config":3922},"Liderança",{"href":3923,"dataGaName":3924,"dataGaLocation":3631},"/company/team/e-group/","leadership",{"text":3926,"config":3927},"Relações com investidores",{"href":3928,"dataGaName":3929,"dataGaLocation":3631},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":3931,"config":3932},"Trust Center",{"href":3933,"dataGaName":3934,"dataGaLocation":3631},"/pt-br/security/","trust center",{"text":3936,"config":3937},"AI Transparency Center",{"href":3938,"dataGaName":3939,"dataGaLocation":3631},"/pt-br/ai-transparency-center/","ai transparency center",{"text":3941,"config":3942},"Boletim informativo",{"href":3943,"dataGaName":3944,"dataGaLocation":3631},"/company/contact/#contact-forms","newsletter",{"text":3946,"config":3947,"menu":3948},"Entre em contato",{"dataNavLevelOne":3894},{"type":3653,"columns":3949},[3950],{"items":3951},[3952,3957,3962],{"text":3953,"config":3954},"Fale com a equipe de vendas",{"href":3955,"dataGaName":3956,"dataGaLocation":3631},"/pt-br/sales/","talk to sales",{"text":3958,"config":3959},"Portal de ajuda",{"href":3960,"dataGaName":3961,"dataGaLocation":3631},"https://support.gitlab.com/hc/en-us","support portal",{"text":3963,"config":3964},"Portal de clientes",{"href":3965,"dataGaName":3966,"dataGaLocation":3631},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":3968,"login":3969,"suggestions":3976},"Fechar",{"text":3970,"link":3971},"Para pesquisar repositórios e projetos, faça login em",{"text":3972,"config":3973},"gitlab.com",{"href":3645,"dataGaName":3974,"dataGaLocation":3975},"search login","search",{"text":3977,"default":3978},"Sugestões",[3979,3982,3987,3989,3994,3999],{"text":3980,"config":3981},"GitLab Duo Agent Platform",{"href":3557,"dataGaName":3980,"dataGaLocation":3975},{"text":3983,"config":3984},"Sugestões de código (AI)",{"href":3985,"dataGaName":3986,"dataGaLocation":3975},"/pt-br/solutions/code-suggestions/","Code Suggestions (AI)",{"text":3663,"config":3988},{"href":3665,"dataGaName":3663,"dataGaLocation":3975},{"text":3990,"config":3991},"GitLab na AWS",{"href":3992,"dataGaName":3993,"dataGaLocation":3975},"/pt-br/partners/technology-partners/aws/","GitLab on AWS",{"text":3995,"config":3996},"GitLab no Google Cloud",{"href":3997,"dataGaName":3998,"dataGaLocation":3975},"/pt-br/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":3716,"config":4000},{"href":3721,"dataGaName":4001,"dataGaLocation":3975},"Why GitLab?",{"freeTrial":4003,"mobileIcon":4008,"desktopIcon":4013,"secondaryButton":4016},{"text":4004,"config":4005},"Iniciar avaliação gratuita",{"href":4006,"dataGaName":3636,"dataGaLocation":4007},"https://gitlab.com/-/trials/new/","nav",{"altText":4009,"config":4010},"Ícone do GitLab",{"src":4011,"dataGaName":4012,"dataGaLocation":4007},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":4009,"config":4014},{"src":4015,"dataGaName":4012,"dataGaLocation":4007},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":4017,"config":4018},"Primeiros passos",{"href":4019,"dataGaName":4020,"dataGaLocation":4007},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/pt-br/get-started/","get started",{"freeTrial":4022,"mobileIcon":4026,"desktopIcon":4028},{"text":4023,"config":4024},"Saiba mais sobre o GitLab Duo",{"href":3557,"dataGaName":4025,"dataGaLocation":4007},"gitlab duo",{"altText":4009,"config":4027},{"src":4011,"dataGaName":4012,"dataGaLocation":4007},{"altText":4009,"config":4029},{"src":4015,"dataGaName":4012,"dataGaLocation":4007},{"button":4031,"mobileIcon":4036,"desktopIcon":4038},{"text":4032,"config":4033},"/switch",{"href":4034,"dataGaName":4035,"dataGaLocation":4007},"#contact","switch",{"altText":4009,"config":4037},{"src":4011,"dataGaName":4012,"dataGaLocation":4007},{"altText":4009,"config":4039},{"src":4040,"dataGaName":4012,"dataGaLocation":4007},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":4042,"mobileIcon":4047,"desktopIcon":4049},{"text":4043,"config":4044},"Voltar para preços",{"href":3814,"dataGaName":4045,"dataGaLocation":4007,"icon":4046},"back to pricing","GoBack",{"altText":4009,"config":4048},{"src":4011,"dataGaName":4012,"dataGaLocation":4007},{"altText":4009,"config":4050},{"src":4015,"dataGaName":4012,"dataGaLocation":4007},{"title":4052,"button":4053,"config":4056},"GitLab Orbit chegou: a camada de contexto para agentes de IA.",{"text":3719,"config":4054},{"href":3705,"dataGaName":4055,"dataGaLocation":3631},"orbit",{"layout":4057,"disabled":3609},"release",{"data":4059},{"text":4060,"source":4061,"edit":4067,"contribute":4072,"config":4077,"items":4082,"minimal":4300},"Git é uma marca comercial da Software Freedom Conservancy e nosso uso do nome \"GitLab\" está sob licença",{"text":4062,"config":4063},"Ver a origem da página",{"href":4064,"dataGaName":4065,"dataGaLocation":4066},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":4068,"config":4069},"Editar esta página",{"href":4070,"dataGaName":4071,"dataGaLocation":4066},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":4073,"config":4074},"Contribua",{"href":4075,"dataGaName":4076,"dataGaLocation":4066},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":4078,"facebook":4079,"youtube":4080,"linkedin":4081},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[4083,4129,4184,4226,4267],{"title":3812,"links":4084,"subMenu":4099},[4085,4089,4094],{"text":4086,"config":4087},"Confira os planos",{"href":3814,"dataGaName":4088,"dataGaLocation":4066},"view plans",{"text":4090,"config":4091},"Por que escolher o GitLab Premium?",{"href":4092,"dataGaName":4093,"dataGaLocation":4066},"/pt-br/pricing/premium/","why premium",{"text":4095,"config":4096},"Por que escolher o GitLab Ultimate?",{"href":4097,"dataGaName":4098,"dataGaLocation":4066},"/pt-br/pricing/ultimate/","why ultimate",[4100],{"title":4101,"links":4102},"Entre em contato conosco",[4103,4105,4107,4109,4114,4119,4124],{"text":3953,"config":4104},{"href":3955,"dataGaName":3641,"dataGaLocation":4066},{"text":3958,"config":4106},{"href":3960,"dataGaName":3961,"dataGaLocation":4066},{"text":3963,"config":4108},{"href":3965,"dataGaName":3966,"dataGaLocation":4066},{"text":4110,"config":4111},"Status",{"href":4112,"dataGaName":4113,"dataGaLocation":4066},"https://status.gitlab.com/","status",{"text":4115,"config":4116},"Termos de uso",{"href":4117,"dataGaName":4118,"dataGaLocation":4066},"/terms/","terms of use",{"text":4120,"config":4121},"Aviso de privacidade",{"href":4122,"dataGaName":4123,"dataGaLocation":4066},"/pt-br/privacy/","privacy statement",{"text":4125,"config":4126},"Preferências de cookies",{"dataGaName":4127,"dataGaLocation":4066,"id":4128,"isOneTrustButton":345},"cookie preferences","ot-sdk-btn",{"title":3649,"links":4130,"subMenu":4141},[4131,4133,4135,4137,4139],{"text":3655,"config":4132},{"href":3657,"dataGaName":3651,"dataGaLocation":4066},{"text":3699,"config":4134},{"href":3557,"dataGaName":3701,"dataGaLocation":4066},{"text":3663,"config":4136},{"href":3665,"dataGaName":3666,"dataGaLocation":4066},{"text":3703,"config":4138},{"href":3705,"dataGaName":3706,"dataGaLocation":4066},{"text":3716,"config":4140},{"href":3721,"dataGaName":3722,"dataGaLocation":4066},[4142],{"title":4143,"links":4144},"Tópicos",[4145,4149,4154,4159,4164,4169,4174,4179],{"text":3663,"config":4146},{"href":4147,"dataGaName":4148,"dataGaLocation":4066},"/pt-br/topics/ci-cd/","cicd",{"text":4150,"config":4151},"GitOps",{"href":4152,"dataGaName":4153,"dataGaLocation":4066},"/pt-br/topics/gitops/","gitops",{"text":4155,"config":4156},"DevOps",{"href":4157,"dataGaName":4158,"dataGaLocation":4066},"/pt-br/topics/devops/","devops",{"text":4160,"config":4161},"Controle de versão",{"href":4162,"dataGaName":4163,"dataGaLocation":4066},"/pt-br/topics/version-control/","version control",{"text":4165,"config":4166},"DevSecOps",{"href":4167,"dataGaName":4168,"dataGaLocation":4066},"/pt-br/topics/devsecops/","devsecops",{"text":4170,"config":4171},"De nuvem nativa",{"href":4172,"dataGaName":4173,"dataGaLocation":4066},"/pt-br/topics/cloud-native/","cloud native",{"text":4175,"config":4176},"IA para programação",{"href":4177,"dataGaName":4178,"dataGaLocation":4066},"/pt-br/topics/devops/ai-for-coding/","ai for coding",{"text":4180,"config":4181},"IA agêntica",{"href":4182,"dataGaName":4183,"dataGaLocation":4066},"/pt-br/topics/agentic-ai/","agentic ai",{"title":3724,"links":4185},[4186,4188,4190,4192,4194,4196,4198,4200,4202,4204,4206,4208,4210,4213,4217,4222],{"text":3737,"config":4187},{"href":3739,"dataGaName":3740,"dataGaLocation":4066},{"text":3742,"config":4189},{"href":3744,"dataGaName":3745,"dataGaLocation":4066},{"text":3747,"config":4191},{"href":3749,"dataGaName":3750,"dataGaLocation":4066},{"text":3773,"config":4193},{"href":3775,"dataGaName":3776,"dataGaLocation":4066},{"text":3778,"config":4195},{"href":3780,"dataGaName":3781,"dataGaLocation":4066},{"text":3783,"config":4197},{"href":3785,"dataGaName":3786,"dataGaLocation":4066},{"text":3788,"config":4199},{"href":3790,"dataGaName":3791,"dataGaLocation":4066},{"text":3793,"config":4201},{"href":3795,"dataGaName":3796,"dataGaLocation":4066},{"text":3798,"config":4203},{"href":3800,"dataGaName":3801,"dataGaLocation":4066},{"text":3755,"config":4205},{"href":3757,"dataGaName":3758,"dataGaLocation":4066},{"text":3760,"config":4207},{"href":3762,"dataGaName":3763,"dataGaLocation":4066},{"text":3765,"config":4209},{"href":3767,"dataGaName":3768,"dataGaLocation":4066},{"text":4150,"config":4211},{"href":4212,"dataGaName":4153,"dataGaLocation":4066},"/pt-br/solutions/gitops/",{"text":4214,"config":4215},"Análise de composição de software",{"href":3693,"dataGaName":4216,"dataGaLocation":4066},"software composition analysis",{"text":4218,"config":4219},"Gestão de fluxo de valor do GitLab",{"href":4220,"dataGaName":4221,"dataGaLocation":4066},"/pt-br/solutions/value-stream-management/","value stream management",{"text":4223,"config":4224},"Todas as soluções",{"href":3731,"dataGaName":4225,"dataGaLocation":4066},"all solutions",{"title":3817,"links":4227},[4228,4233,4238,4240,4242,4244,4246,4248,4250,4252,4254,4256,4258,4260,4263],{"text":4229,"config":4230},"Instalação",{"href":4231,"dataGaName":4232,"dataGaLocation":4066},"/pt-br/install/","install",{"text":4234,"config":4235},"Guias de início rápido",{"href":4236,"dataGaName":4237,"dataGaLocation":4066},"/pt-br/get-started/","quick setup checklists",{"text":3830,"config":4239},{"href":3832,"dataGaName":3833,"dataGaLocation":4066},{"text":3835,"config":4241},{"href":3837,"dataGaName":3838,"dataGaLocation":4066},{"text":3840,"config":4243},{"href":3842,"dataGaName":3843,"dataGaLocation":4066},{"text":3845,"config":4245},{"href":3847,"dataGaName":3848,"dataGaLocation":4066},{"text":3850,"config":4247},{"href":3852,"dataGaName":3853,"dataGaLocation":4066},{"text":3858,"config":4249},{"href":3860,"dataGaName":3861,"dataGaLocation":4066},{"text":3863,"config":4251},{"href":3865,"dataGaName":3866,"dataGaLocation":4066},{"text":3868,"config":4253},{"href":3870,"dataGaName":3871,"dataGaLocation":4066},{"text":3873,"config":4255},{"href":3875,"dataGaName":3876,"dataGaLocation":4066},{"text":3878,"config":4257},{"href":3880,"dataGaName":3881,"dataGaLocation":4066},{"text":3941,"config":4259},{"href":3943,"dataGaName":3944,"dataGaLocation":4066},{"text":4261,"config":4262},"Novidades",{"href":3889,"dataGaName":3890,"dataGaLocation":4066},{"text":4264,"config":4265},"Todos os recursos",{"href":3824,"dataGaName":4266,"dataGaLocation":4066},"all resources",{"title":3892,"links":4268},[4269,4271,4273,4275,4277,4279,4281,4283,4285,4290,4295],{"text":3900,"config":4270},{"href":3902,"dataGaName":3894,"dataGaLocation":4066},{"text":3905,"config":4272},{"href":3907,"dataGaName":3908,"dataGaLocation":4066},{"text":3911,"config":4274},{"href":3913,"dataGaName":3914,"dataGaLocation":4066},{"text":3916,"config":4276},{"href":3918,"dataGaName":3919,"dataGaLocation":4066},{"text":3921,"config":4278},{"href":3923,"dataGaName":3924,"dataGaLocation":4066},{"text":3926,"config":4280},{"href":3928,"dataGaName":3929,"dataGaLocation":4066},{"text":3931,"config":4282},{"href":3933,"dataGaName":3934,"dataGaLocation":4066},{"text":3936,"config":4284},{"href":3938,"dataGaName":3939,"dataGaLocation":4066},{"text":4286,"config":4287},"Sustentabilidade",{"href":4288,"dataGaName":4289,"dataGaLocation":4066},"/sustainability/","Sustainability",{"text":4291,"config":4292},"Diversidade, inclusão e pertencimento (DIB)",{"href":4293,"dataGaName":4294,"dataGaLocation":4066},"/pt-br/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":4296,"config":4297},"Declaração de Transparência da Lei da Escravidão Moderna",{"href":4298,"dataGaName":4299,"dataGaLocation":4066},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":4301},[4302,4305,4308],{"text":4303,"config":4304},"Termos",{"href":4117,"dataGaName":4118,"dataGaLocation":4066},{"text":4306,"config":4307},"Cookies",{"dataGaName":4127,"dataGaLocation":4066,"id":4128,"isOneTrustButton":345},{"text":4309,"config":4310},"Privacidade",{"href":4122,"dataGaName":4123,"dataGaLocation":4066},[4312],{"id":4313,"title":7,"body":3608,"config":4314,"content":4316,"description":3608,"extension":4320,"meta":4321,"navigation":345,"path":4322,"seo":4323,"stem":4324,"__hash__":4325},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":4315},"BlogAuthor",{"name":7,"config":4317},{"headshot":4318,"ctfId":4319},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi","yml",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[4327],{"title":4328,"description":4329,"heroImage":4330,"category":3604,"date":4331,"authors":4332,"slug":4333,"externalUrl":3608},"Claude Code e o GitLab: três fluxos de trabalho que realmente entregam","Agentes de IA escrevem código rápido. Mas quem entrega? Veja como Claude Code e GitLab Duo Agent Platform vão do relato do bug à correção em produção.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1778087000/qdfsf8kz6ebfnopx9v2e.png","2026-05-06",[7],"claude-code-and-gitlab",{"header":4335,"blurb":4336,"button":4337,"secondaryButton":4342},"Comece a desenvolver mais rápido hoje","Veja o que sua equipe pode fazer com a plataforma de orquestração inteligente para DevSecOps.\n",{"text":4338,"config":4339},"Obtenha sua avaliação gratuita",{"href":4340,"dataGaName":3636,"dataGaLocation":4341},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/pt-br/","feature",{"text":3953,"config":4343},{"href":3955,"dataGaName":3641,"dataGaLocation":4341},{"header":4345,"text":4346,"button":4350,"disclaimer":4353},"Comece sua avaliação gratuita de\n\n30 dias do GitLab\n",[4347,4348,4349],"Acesso à GitLab Duo Agent Platform","Experimente nossos recursos mais avançados","Automatize tarefas complexas com IA",{"text":3633,"config":4351},{"href":4352,"dataGaName":3636,"dataGaLocation":3861},"https://gitlab.com/-/trials/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/pt-br/blog/","Não é necessário cartão de crédito.",1790800491757]