Track
/
Go
Go
/
Esercizi
/
Analisi dei file di log
Analisi dei file di log

Analisi dei file di log

Esercizio di apprendimento

Introduzione

Il pacchetto regexp offre supporto per le espressioni regolari in Go.

Sintassi

La sintassi delle espressioni regolari accettate è la stessa sintassi generale usata da Perl, Python e altri linguaggi.

Sia i pattern di ricerca sia i testi di input vengono interpretati come UTF-8.

Quando si usano i backtick (`) per creare le stringhe, le barre rovesciate (\) non hanno alcun significato speciale e non indicano l'inizio di caratteri speciali come le tabulazioni \t o le nuove righe \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'

Per questo motivo conviene usare i backtick per creare le espressioni regolari, perché così non dobbiamo fare l'escape delle barre rovesciate:

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

Compilare i pattern: il tipo RegExp

Per usare un'espressione regolare, dobbiamo prima compilare il pattern. Per compilazione qui si intende prendere il pattern dell'espressione regolare come stringa e convertirlo in una rappresentazione interna con cui è più facile lavorare. Dobbiamo compilare ogni pattern una sola volta: dopodiché possiamo usare la versione compilata dell'espressione regolare tutte le volte che vogliamo. Il tipo regexp.Regexp rappresenta un'espressione regolare compilata. Possiamo compilare un pattern in forma di stringa in un regexp.Regexp usando la funzione regexp.Compile. Questa funzione restituisce nil e un errore se la compilazione non è riuscita:

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 funzione MustCompile è un'alternativa comoda a Compile:

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

Usando questa funzione, non serve gestire l'errore.

Caution

MustCompile va usato solo quando siamo sicuri che il pattern venga compilato, altrimenti il programma andrà in panic.

I metodi delle espressioni regolari

Ci sono 16 metodi di Regexp che cercano le corrispondenze di un'espressione regolare e identificano il testo trovato. I loro nomi corrispondono a questa espressione regolare:

Find(All)?(String)?(Submatch)?(Index)?
  • Se All è presente, il metodo trova le corrispondenze successive non sovrapposte dell'intera espressione.
  • Se String è presente, l'argomento è una stringa; altrimenti è una slice di byte; i valori restituiti vengono adattati di conseguenza.
  • Se Submatch è presente, il valore restituito è una slice che identifica le sottocorrispondenze successive dell'espressione.
  • Se Index è presente, le corrispondenze e le sottocorrispondenze sono identificate da coppie di indici di byte all'interno della stringa di input.

Ci sono anche metodi per:

  • sostituire le corrispondenze delle espressioni regolari con stringhe di sostituzione;
  • dividere stringhe separate da espressioni regolari.

Nel complesso, il pacchetto regexp definisce più di 40 funzioni e metodi. Qui di seguito mostreremo l'uso di alcuni metodi. Per i dettagli su queste e altre funzioni, consulta la documentazione dell'API.

Esempi di MatchString

Il metodo MatchString indica se una stringa contiene almeno una corrispondenza di un'espressione regolare.

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

Esempi di FindString

Il metodo FindString restituisce una stringa contenente il testo della corrispondenza più a sinistra dell'espressione regolare.

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")     // => ""

Esempi di FindStringSubmatch

Il metodo FindStringSubmatch restituisce una slice di stringhe contenente il testo della corrispondenza più a sinistra dell'espressione regolare e le corrispondenze, se presenti, delle sue sottoespressioni. Può essere usato per identificare le stringhe che corrispondono ai gruppi di cattura. Un valore restituito pari a nil indica che non c'è alcuna corrispondenza.

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>

Esempi di ReplaceAllString

Il metodo re.ReplaceAllString(src,repl) restituisce una copia di src, sostituendo le corrispondenze dell'espressione regolare re con la stringa di sostituzione 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"

Esempi di Split

Il metodo re.Split(s,n) divide un testo s in sottostringhe separate dall'espressione e restituisce una slice delle sottostringhe comprese tra quelle corrispondenze. Il conteggio n determina il numero massimo di sottostringhe da restituire. Se n<0, il metodo restituisce tutte le sottostringhe.

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"}

Istruzioni

Questo esercizio riguarda il parsing dei file di log.

Dopo una recente revisione di sicurezza, ti è stato chiesto di ripulire i file di log archiviati dell'organizzazione.

Tutte le stringhe passate alle funzioni sono garantite non nulle e senza spazi all'inizio e alla fine.

1. Identificare le righe di log corrotte

Ti serve un'idea di quante righe di log nel tuo archivio non rispettino gli standard attuali. Ritieni che un semplice test riveli se una riga di log è valida. Per essere considerata valida, una riga deve iniziare con una delle seguenti stringhe:

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

Implementa la funzione IsValidLine in modo che restituisca false se una stringa non è valida, altrimenti true.

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

2. Dividere la riga di log

Un nuovo team si è unito all'organizzazione e hai scoperto che i suoi file di log usano uno strano separatore per i «campi». Invece di qualcosa di sensato come i due punti ":", usano una stringa come "<--->" o "<=>" (perché è più carina): in pratica qualsiasi stringa che abbia come primo carattere "<", come ultimo carattere ">" e, nel mezzo, qualsiasi combinazione dei seguenti caratteri "~", "*", "=" e "-".

Implementa la funzione SplitLogLine che prende una riga e restituisce un array di stringhe, ognuna delle quali contiene un campo.

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

3. Contare il numero di righe che contengono password in un testo tra virgolette

Il team ha bisogno di conoscere i riferimenti alle password nei testi tra virgolette, così da poterli esaminare manualmente.

Implementa la funzione CountQuotedPasswords per fornire un'indicazione della probabile entità del lavoro manuale.

Individua le righe di log in cui la stringa "password", che può essere in qualsiasi combinazione di maiuscole e minuscole, è racchiusa tra virgolette. Devi tenere conto della possibilità che ci sia altro contenuto tra le virgolette, prima e dopo "password". Ogni riga conterrà al massimo due virgolette.

Le righe passate alla routine possono essere valide oppure no, secondo la definizione del punto 1. Le elaboriamo allo stesso modo, che siano valide o no.

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. Rimuovere gli artefatti dal log

Hai scoperto che una qualche elaborazione a monte dei log ha disseminato in tutti i log il testo "end-of-line" seguito da un numero di riga (senza spazio in mezzo).

Implementa la funzione RemoveEndOfLineText in modo che prende una stringa, rimuova il testo end-of-line e restituisca una stringa «pulita».

Le righe che non contengono il testo end-of-line devono essere restituite senza modifiche.

Rimuovi soltanto la stringa end-of-line. Non provare a sistemare gli spazi bianchi.

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

5. Etichettare le righe con i nomi utente

Hai notato che alcune righe di log contengono frasi che si riferiscono a utenti. Queste frasi contengono sempre la stringa "User", seguita da uno o più spazi e poi da un nome utente. Decidi di etichettare queste righe.

Implementa una funzione TagWithUserName che elabora le righe di log:

  • Le righe che non contengono la stringa "User " restano invariate.
  • Per le righe che contengono la stringa "User ", anteponi alla riga [USR] seguito dal nome utente.

Ad esempio:

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."
// }

Puoi dare per scontato che:

  • I nomi utente siano seguiti da almeno un carattere di spaziatura nel log.
  • C'è al massimo un'occorrenza della stringa "User " in ogni riga.
  • I nomi utente siano stringhe non vuote che non contengono spazi bianchi.
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Go Exercism

Vuoi iniziare Analisi dei file di log?

Iscriviti a Exercism per imparare e padroneggiare Go con 34 concetti165 esercizi e il mentoring di persone reali, tutto gratis.