Parcours
/
Go
Go
/
Exercices
/
Analyse les fichiers de logs
Analyse les fichiers de logs

Analyse les fichiers de logs

Exercice d'apprentissage

Introduction

Le paquet regexp prend en charge les expressions régulières en Go.

Syntaxe

La syntaxe des expressions régulières acceptées est la même syntaxe générale que celle utilisée par Perl, Python et d'autres langages.

Les motifs de recherche comme les textes d'entrée sont interprétés en UTF-8.

Quand on utilise des accents graves (`) to make strings, backslashes (\) don't have any special meaning and don't mark the beginning of special characters like tabs \t or newlines \n :

"\t\n" // regular string literal with 2 characters: a tab and a newline
`\t\n`// raw string literal with 4 characters: two backslashes, a 't', and an 'n'

C'est pourquoi il est préférable d'utiliser les accents graves pour écrire des expressions régulières, car on n'a alors pas besoin d'échapper les barres obliques inverses :

"\\" // string with a single backslash
`\\` // string with 2 backslashes

Compiler les motifs : le type RegExp

Pour utiliser une expression régulière, il faut d'abord compiler le motif sous forme de string. La compilation consiste ici à prendre le motif de l'expression régulière sous forme de string et à le convertir en une représentation interne plus facile à manipuler. Il suffit de compiler chaque motif une seule fois ; ensuite, on peut utiliser autant de fois que nécessaire la version compilée de l'expression régulière. Le type regexp.Regexp représente une expression régulière compilée. On peut compiler un motif sous forme de string en un regexp.Regexp grâce à la fonction regexp.Compile. Cette fonction renvoie nil et une erreur si la compilation échoue :

re, err := regexp.Compile(`(a|b)+`)
fmt.Println(re, err) // => (a|b)+ <nil>
re, err = regexp.Compile(`a|b)+`)
fmt.Println(re, err) // => <nil> error parsing regexp: unexpected ): `a|b)+`

La fonction MustCompile est une alternative pratique à Compile :

re = regexp.MustCompile(`[a-z]+\d*`)

Avec cette fonction, il n'est pas nécessaire de gérer une erreur.

Caution

MustCompile ne doit être utilisée que lorsqu'on est certain que le motif se compile, sinon le programme panique.

Les méthodes des expressions régulières

Il existe 16 méthodes de Regexp qui appliquent une expression régulière et identifient le texte correspondant. Leurs noms correspondent à cette expression régulière :

Find(All)?(String)?(Submatch)?(Index)?
  • Si All est présent, la méthode recherche les correspondances successives sans chevauchement de l'expression entière.
  • Si String est présent, l'argument est une string ; sinon, c'est un slice d'octets ; les valeurs de retour sont adaptées en conséquence.
  • Si Submatch est présent, la valeur de retour est un slice qui identifie les sous-correspondances successives de l'expression.
  • Si Index est présent, les correspondances et les sous-correspondances sont identifiées par des paires d'indices d'octets dans la string d'entrée.

Il existe aussi des méthodes pour :

  • remplacer les correspondances d'expressions régulières par des strings de remplacement, et
  • découper des strings séparées par des expressions régulières.

Au total, le paquet regexp définit plus de 40 fonctions et méthodes. On va illustrer ci-dessous l'utilisation de quelques méthodes. Consulte la documentation de l'API pour plus de détails sur ces fonctions et sur les autres.

Exemples d'utilisation de MatchString

La méthode MatchString indique si une string contient au moins une correspondance de l'expression régulière.

re = regexp.MustCompile(`[a-z]+\d*`)
b = re.MatchString("[a12]")       // => true
b = re.MatchString("12abc34(ef)") // => true
b = re.MatchString(" abc!")       // => true
b = re.MatchString("123 456")     // => false

Exemples d'utilisation de FindString

La méthode FindString renvoie une string contenant le texte de la correspondance la plus à gauche de l'expression régulière.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.FindString("[a12]")       // => "a12"
s = re.FindString("12abc34(ef)") // => "abc34"
s = re.FindString(" abc!")       // => "abc"
s = re.FindString("123 456")     // => ""

Exemples d'utilisation de FindStringSubmatch

La méthode FindStringSubmatch renvoie un slice de strings contenant le texte de la correspondance la plus à gauche de l'expression régulière ainsi que les correspondances, s'il y en a, de ses sous-expressions. On peut s'en servir pour identifier les strings qui correspondent aux groupes de capture. Une valeur de retour de nil indique qu'il n'y a pas de correspondance.

re = regexp.MustCompile(`[a-z]+(\d*)`)
sl = re.FindStringSubmatch("[a12]")       // => []string{"a12","12"}
sl = re.FindStringSubmatch("12abc34(ef)") // => []string{"abc34","34"}
sl = re.FindStringSubmatch(" abc!")       // => []string{"abc",""}
sl = re.FindStringSubmatch("123 456")     // => <nil>

Exemples d'utilisation de ReplaceAllString

La méthode re.ReplaceAllString(src,repl) renvoie une copie de src, en remplaçant les correspondances de l'expression régulière re par la string de remplacement repl.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.ReplaceAllString("[a12]", "X")       // => "[X]"
s = re.ReplaceAllString("12abc34(ef)", "X") // => "12X(X)"
s = re.ReplaceAllString(" abc!", "X")       // => " X!"
s = re.ReplaceAllString("123 456", "X")     // => "123 456"

Exemples d'utilisation de Split

La méthode re.Split(s,n) découpe un texte s en sous-chaînes séparées par l'expression et renvoie un slice des sous-chaînes comprises entre ces correspondances. La valeur n détermine le nombre maximal de sous-chaînes à renvoyer. Si n<0, la méthode renvoie toutes les sous-chaînes.

re = regexp.MustCompile(`[a-z]+\d*`)
sl = re.Split("[a12]", -1)      // => []string{"[","]"}
sl = re.Split("12abc34(ef)", 2) // => []string{"12","(ef)"}
sl = re.Split(" abc!", -1)      // => []string{" ","!"}
sl = re.Split("123 456", -1)    // => []string{"123 456"}

Instructions

Cet exercice porte sur l'analyse de fichiers de logs.

À la suite d'un récent audit de sécurité, on t'a demandé de nettoyer les fichiers de logs archivés de l'organisation.

Toutes les strings passées aux fonctions sont garanties non nulles et sans espaces au début ni à la fin.

1. Identifie les lignes de log corrompues

Tu as besoin de te faire une idée du nombre de lignes de log de ton archive qui ne respectent pas les normes actuelles. Tu penses qu'un test simple permet de savoir si une ligne de log est valide. Pour être considérée comme valide, une ligne doit commencer par l'une des strings suivantes :

  • [TRC]
  • [DBG]
  • [INF]
  • [WRN]
  • [ERR]
  • [FTL]

Implémente la fonction IsValidLine pour qu'elle renvoie false si une string n'est pas valide, et true sinon.

IsValidLine("[ERR] A good error here")
// => true
IsValidLine("Any old [ERR] text")
// => false
IsValidLine("[BOB] Any old text")
// => false

2. Découpe la ligne de log

Une nouvelle équipe a rejoint l'organisation, et tu découvres que ses fichiers de logs utilisent un séparateur étrange pour les « champs ». Au lieu de quelque chose de raisonnable comme le deux-points « : », ils utilisent une string telle que « <---> » ou « <=> » (parce que c'est plus joli), en fait n'importe quelle string dont le premier caractère est « < », dont le dernier caractère est « > » et qui contient, entre les deux, n'importe quelle combinaison des caractères « ~ », « * », « = » et « - ».

Implémente la fonction SplitLogLine qui prend une ligne et renvoie un tableau de chaînes de caractères contenant chacune un champ.

SplitLogLine("section 1<*>section 2<~~~>section 3")
// => []string{"section 1", "section 2", "section 3"},

3. Compte le nombre de lignes contenant password dans du texte entre guillemets

L'équipe a besoin de connaître les références aux mots de passe dans le texte entre guillemets afin qu'elles puissent être examinées manuellement.

Implémente la fonction CountQuotedPasswords pour donner une idée de l'ampleur probable du travail manuel.

Identifie les lignes de log où la string « password », qui peut être écrite dans n'importe quelle combinaison de majuscules et de minuscules, est entourée de guillemets. Tu dois tenir compte de la possibilité qu'il y ait du contenu supplémentaire entre les guillemets, avant et après « password ». Chaque ligne contient au plus deux guillemets.

Les lignes passées à la routine peuvent être valides ou non au sens de la tâche 1. On les traite de la même façon, qu'elles soient valides ou non.

lines := []string{
    `[INF] passWord`, // contains 'password' but not surrounded by quotation marks
    `"passWord"`,  // count this one
    `[INF] User saw error message "Unexpected Error" on page load.`, // does not contain 'password'
    `[INF] The message "Please reset your password" was ignored by the user`, // count this one
}
// => 2

4. Supprime les artefacts des logs

Tu as découvert qu'un traitement des logs en amont sème un peu partout le texte « end-of-line » suivi d'un numéro de ligne (sans espace entre les deux).

Implémente la fonction RemoveEndOfLineText qui prend une string, en supprime le texte de fin de ligne et renvoie une string « propre ».

Les lignes qui ne contiennent pas de texte de fin de ligne doivent être renvoyées sans modification.

Supprime uniquement la string de fin de ligne. N'essaie pas d'ajuster les espaces.

RemoveEndOfLineText("[INF] end-of-line23033 Network Failure end-of-line27")
// => "[INF]  Network Failure "

5. Étiquette les lignes avec les noms d'utilisateur

Tu as remarqué que certaines lignes de log contiennent des phrases qui font référence à des utilisateurs. Ces phrases contiennent toujours la string "User", suivie d'un ou plusieurs espaces, puis d'un nom d'utilisateur. Tu décides d'étiqueter ces lignes.

Implémente une fonction TagWithUserName qui traite les lignes de log :

  • Les lignes qui ne contiennent pas la string "User " restent inchangées.
  • Pour les lignes qui contiennent la string "User ", préfixe la ligne par [USR] suivi du nom d'utilisateur.

Par exemple :

result := TagWithUserName([]string{
    "[WRN] User James123 has exceeded storage space.",
	"[WRN] Host down. User   Michelle4 lost connection.",
	"[INF] Users can login again after 23:00.",
	"[DBG] We need to check that user names are at least 6 chars long.",
})
// => []string {
//  "[USR] James123 [WRN] User James123 has exceeded storage space.",
//  "[USR] Michelle4 [WRN] Host down. User   Michelle4 lost connection.",
//  "[INF] Users can login again after 23:00.",
//  "[DBG] We need to check that user names are at least 6 chars long."
// }

Tu peux supposer que :

  • Les noms d'utilisateur sont suivis d'au moins un espace dans le log.
  • La string "User " apparaît au plus une fois par ligne.
  • Les noms d'utilisateur sont des strings non vides qui ne contiennent pas d'espace.
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Go Exercism

Prêt à commencer Analyse les fichiers de logs ?

Inscris-toi sur Exercism pour apprendre et maîtriser Go avec 34 concepts165 exercices, et un vrai mentorat humain, le tout gratuitement.