Analizziamo i Log del Webserver (Nginx) e visualizziamo i risultati su QGIS per un po’ di GeoMarketing

Ciao a tuttə e bentrovatə, sono “reduce” da un corso introduttivo su QGIS e ho pensato di “frullare” le competenze appena acquisite con l’analisi dei log del webserver dal mio sito (Nginx nel caso specifico ma, visto che utilizziamo il formato combined, presumo che sia applicabile anche a Apache, con pochissimi ritocchi… ne riparleremo più avanti).

Avevo già scritto un articolo in merito utilizzando ElasticSearch e Kibana (e Vi invito a dargli una lettura, sebbene ormai sia un po’ vecchiotto) e parte di questo articolo (principalmente il setup dell’ambiente) è sovrapponibile al precedente articolo del blog (ChatBot in Python con Gradio e Google Gemma sfruttando Hugging Face).

Come sempre ho creato un repo su GitHub per chi non avesse troppa voglia di copioincollare codice rischiando di sbagliare ai ritorni di riga e simile.

A scanso di equivoci vorrei informare che sto lavorando su una macchina con installato Windows 11 ma che il presente tutorial e utilizzabile anche su MacOS e Linux (ovunque siano disponibili: l’ambiente Python, un editor o un IDE per programmare e QGIS).

Andiamo ad iniziare…

Setup dell’Ambiente

Installazione di Python

Se non avete già questo bellissimo pezzo di FOSS sul Vs. sistema le strade sono due:

Forse per un neofita (non che io mi senta un “arrivato”) è più semplice il primo metodo in quanto nel secondo bisogna settare un paio di opzioni nell’installer ufficiale (ma niente di cui spaventarsi troppo 🙂 ).

Installiamo VS Code

Per adesso sto restando affascinato dalla flessibilità di Visual Studio Code: penso che metterò PyCharm da parte (ma solo per un po’; anche lui ha la sua buona dose di elementi a suo favore 😉 ).

Quindi iniziamo scaricando VS Code dalla pagina ufficiale di download e scarichiamo la versione per Windows (sono disponibili varie versioni: a me fa più simpatia la versione User Installer che installa il software solo per l’utente corrente ma c’è anche la System Installer che installa VS Code per tutti gli utenti del sistema).

Vale la pena di spendere due parole sul primo avvio di questo potente editor; all’avvio verrà individuata la lingua di sistema e verrà chiesto se si vuole VS Code tradotto in italiano oppure se lo si vuole lasciare in inglese: questa è una scelta personale e la lascio sulle vostre spalle 🙂 .

Se si è scelto di installare la traduzione il software avvertirà che è necessario ricaricare il programma e così cliccate su ricarica.

Un altro dettaglio è che VS Code propone una serie di scelte inizialmente: leggete con attenzione e scegliete sicuramente il Dark Mode (altrimenti vi abbronzerete troppo durante lo sviluppo 😉 ) e gli altri settaggi che vi fanno più comodo.

Installiamo l’estensione per Python in VS Code

Sulla sinistra si trovano una serie di icone; al primo click si aprono e, se ri-clickate (bruttissimo neologismo) si richiudono: vi conviene dare un’occhiata in quanto (alle volte) l’installazione di un’estensione in VS Code aggiunge anche un’icona sulla sinistra.

A noi interessa il pannello delle estensioni (a cui si accede anche con CTRL + MAIUSCOLO + X).

Una volta aperto cercate “Python” nella barra di ricerca delle estensioni (sono veramente una marea) e installate quella che si chiama proprio così e che è stata sviluppata da Microsoft stessa (siccome vengono ordinate in ordine di popolarità è la prima dall’alto).

Account su MaxMind e download del GeoDatabase degli indirizzi IP

Esistono vari modi per geolocalizzare un indirizzo IP; il più economico (ma anche un po’ inaccurato, nella versione free) è sicuramente MaxMind.

Per completezza dovrei anche citare Shodan ma il suo utilizzo va decisamente oltre gli scopi di questo tutorial.

Come prima cosa dobbiamo creare un account sul sito (qui un articolo esaustivo della stessa MaxMind: https://support.maxmind.com/hc/en-us/articles/4407099783707-Create-an-Account)… tenete conto che io ho creato un account abbastanza tempo fa e quindi, molti passaggi sono un po’ cambiati.

A quanto leggo, visto che noi utilizzeremo il loro database (per evitare di fare una marea di chiamate; anche perché nella versione free è limitato a 5.000 chiamate API al giorno), basta andare su: https://www.maxmind.com/en/geolite2/signup?utm_source=kb&utm_medium=kb-link&utm_campaign=kb-create-account e rispondere alle domande per creare un account.

Ad account creato bisogna scaricare il database; esistono vari formati ma, secondo me, il sistema più comodo è scaricare il loro database in formato binario (sono pochi Megabyte) che contiene tutti gli IP (occhio che viene aggiornato abbastanza spesso).

Una volta creato l’account, effettuato il login (occhio al codice di verifica per email) basta andare su “Download Files”, nella sidebar di sinistra, e abbiamo a disposizione tutti i database.

La spiegazione di tutti i database e del loro contenuto esula dagli scopi di questo Tutorial (anche perché è spiegata benissimo nella documentazione ufficiale di MaxMind)… a noi interessa scaricare: GeoLite2 City (basta cliccare, nella riga corrispondente su: “Download GZIP“).

Alla data in cui scrivo, il file GeoLite2-City_20240329.tar.gz è grande poco più di 27 Megabyte (ma è compresso con gzip); alla sua estrazione diventa circa 56 Megabytes (come si intuisce dal nome è aggiornato al 29/03/2024).

Estraetelo in una qualche maniera (Winrar va più che benissimo 🙂 ) e ci interessa il file con estensione .mmdb.

Procuriamoci dei log di Nginx

Qui devo necessariamente essere un po’ più “fumoso”; molto dipende dal Vs. ambiente di hosting: io utilizzo l’ottimo WordOps (uno stack per l’installazione e la gestione di siti WordPress ad alte prestazioni) e trovo i log del mio sito dentro /var/log/nginx (che, comunque, è un buon posto per iniziare a cercare); altri hosting Vi daranno la possibilità di scaricare i log senza “sporcarvi le mani” con comandi della bash di Linux o simili.

IMPORTANTE: i log devono essere in formato combined (qui un breve approfondimento), altrimenti la RegEx nel codice seguente non funzionerà correttamente.

IMPORTANTE: i log (normalmente) sono più di un file in quanto vengono “rotati” e compressi… per una prima prova vi basterà scaricare quello che ha estensione .access.log ma, se doveste fare un’analisi temporale completa, avrete bisogno di tutti i log e dovrete:

  1. Scomprimerli uno per uno
  2. Metterli uno di seguito l’altro con l’ottimo VS Code in ordine cronologico (vi potete aiutare con il datetime ma anche con il nome del file)
  3. Salvare di nuovo il tutto con un’estensione .access.log (in realtà potreste anche salvarla come un semplice .txt e poi modificare lo script fornito per farlo puntare al nome che gli avete dato)

Creiamo il Virtual Environment (venv) per Python in VS Code

Il procedimento, se avete installato sia Python dal Microsoft Store che l’estensione Python per VS Code, si riduce a un paio di click e quindi la faccio breve:

  1. Aprite VS Code
  2. Create una cartella dove più vi aggrada e chiamatela, ad es., LogAnalyzer (C:\ potrebbe essere un buon posto ma anche il Desktop ha i suoi vantaggi, specie se backuppate tutto con OneDrive 🙂 )
  3. Aggiungete una cartella all’area di lavoro (è la prima icona in alto a sinistra in VS Code; si può accedere anche con CTRL + MAIUSC + E)
  4. Sempre sulla barra delle icone sulla sinistra cliccate sull’icona di Python (che è spuntata quando avete installato l’estensione per Python in VS Code; occhio che, se non Vi spunta, forse è necessario ricaricare VS Code)
  5. Cliccate sul + in alto a sinistra per creare un nuovo ambiente virtuale
  6. Vi viene chiesto quale Virtual Environment vogliate e scegliete venv
  7. Vi viene chiesto quale interprete Python utilizzare e scegliete quello che avete installato (o da Microsoft Store oppure manualmente dal sito… VS Code è “furbo” e cercherà gli interpreti disponibili nel sistema, anche se, se avete seguito questa guida, ne troverete uno solamente)

Ultimi ritocchi

Di seguito mettete nella cartella appena creata (LogAnalyzer se avete seguito il mio suggerimento):

  • Il database di MaxMind in formato .mmdb (scompresso, eh!!! 🙂 )
  • Il file con i log di Nginx (o Apache) IN FORMATO COMBINED (repetita iuvant, non me ne vogliate)!!!

Quindi andate, sempre da VS Code, ad aprire un terminale (c’è un apposito menù: Terminale e scegliete la prima voce, oppure con la combinazione di tasti: CRTL + MAIUSC + ò; io utilizzo una tastiera italiana e VS Code con la traduzione e, quindi, è possibile che la combinazione di tasti sia diversa per altri casi).

Nel terminale date questo comando (serve ad attivare l’ambiente virtuale che abbiamo creato):

.\.venv\Scripts\Activate.ps1

IMPORTANTE: sebbene VS Code vi dovrebbe proporre di default, come percorso di lavoro, la cartella appena creata (LogAnalyzer, se avete seguito il tutorial) assicuratevi di essere nella cartella appena creata (o il comando per attivare il venv non funzionerà).

A questo punto dobbiamo installare le dipendenze (in realtà qui ne abbiamo bisogno di una sola); è il package Python per leggere e interrogare il database di MaxMind, quindi date quest’altro comando nel Terminale di seguito al precedente:

pip install --upgrade geoip2

Con questo comando verrà installato il pacchetto geoip2 e tutte le sue dipendenze (senza “sporcare” l’ambiente Python principale).

Qui c’è la pagina di PyPI: https://pypi.org/project/geoip2/ da cui potete approfondire e qui c’è la pagina di GitHub mantenuta da MaxMind che spiega i vari modi di utilizzare il pacchetto geoip2.

Il Codice

Per questo tutorial ho preparato un codice che non è l’optimum per effettuare una analisi efficiente dei log… nel prosieguo spiegherò il perché.

Il codice da copiare

import csv
import geoip2.database
import re

# Percorso al file di log di nginx
log_file_path = './ingegnerealbano.com.access.log'

# Definizione del formato del log combined di nginx
log_pattern = re.compile(r'(?P<ip>\S+) \S+ \S+ \[(?P<datetime>[^\]]+)\] (?P<domain>\S+) "(?P<method>\S+) (?P<url>\S+) \S+" (?P<status>\d{3}) (?P<size>\S+) "(?P<referrer>[^"]*)" "(?P<useragent>[^"]*)"')

# Definisci il nome del file CSV
csv_filename = './ingegnerealbano.com.access.log.csv'

# Funzione per analizzare una riga del log
def parse_log_line(line):
    match = log_pattern.match(line)
    if match:
        return match.groupdict()
    else:
        return None

# Lista che conterrà tutti i dati analizzati
log_data_list = []

# Leggere il file di log e analizzare ogni riga
with open(log_file_path, 'r') as file:
    for line in file:
        log_data = parse_log_line(line)
        if log_data:
            with geoip2.database.Reader('GeoLite2-City.mmdb') as reader:
                response = reader.city(log_data['ip'])
                log_data.update({'city': response.city.name})
                log_data.update({'postalcode': response.postal.code})
                log_data.update({'latitude': response.location.latitude})
                log_data.update({'longitude': response.location.longitude})
            log_data_list.append(log_data)

# Se non ci sono dati nel file di log, esci dallo script
if not log_data_list:
    print("Nessun dato nel file di log.")
    exit()

# Definisci gli header del CSV basandoti sulle chiavi del primo dizionario nella lista
fieldnames = log_data_list[0].keys()

# Apre il file CSV in modalità scrittura
with open(csv_filename, 'w', newline='', encoding='utf-8') as csvfile:
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

    # Scrivi l'header del CSV
    writer.writeheader()

    # Scrivi ogni riga del dizionario come una riga nel CSV
    for row in log_data_list:
        writer.writerow(row)

print("CSV generato con successo:", csv_filename)

Questo codice prende un file chiamato “ingegnerealbano.com.access.log” dalla stessa cartella in cui stiamo lavorando (vedi sopra) e genera un file chiamato “ingegnerealbano.com.access.log.csv” sempre nella stessa cartella di lavoro; basta adattare i nomi (alle righe 6 e 12, rispettivamente) per utilizzare il nome dei vostri log e per decidere il nome del file di output.

Il Repository GitHub

Se vi noia copioincollare codice o volete sfruttare l’ottimo GitHub Desktop (o gh, la sua versione CLI) ecco qui il repo da clonare: https://github.com/senzanome75/Python-MaxMind-Nginx-Log-Analyzer.

Georeferenziamo un po’

Due parole sui GIS

Prima di iniziare conviene spendere due parole sul mondo dei GIS (o SIT, in Italiano): GIS è un acronimo che sta per Geographical Information System (o Sistema Informativo Territoriale, se proprio vogliamo difendere a spada tratta la nostra bella lingua italiana 🙂 ); non è mia intenzione dilungarmi eccessivamente sull’argomento: esistono parecchi e voluminosi tomi che lo sapranno fare meglio di me 🙂 .

Il concetto di fondo (quello che vorrei che passasse) è che se abbiamo dei dati (ad esempio le vendite di una catena di supermarket) e se a questi dati possiamo attribuire una posizione (intesa proprio come Longitudine e Latitudine) allora possiamo georiferire questi dati.

Possiamo, cioè, mapparli.

A cosa serve?

È abbastanza intuitivo che, mentre i dati in un comune database (ad es. MySQL) sono già un valore, se riusciamo ad aggiungere le coordinate possiamo anche “vederli” (potremmo, ad es., notare che, in quella catena di supermarket, la filiale posizionata in una certa zona vende maggiormente un prodotto rispetto ad altre filiali).

In effetti, oltre a numerose e complesse analisi di ogni tipo, i GIS possono anche essere catalogati anche come Sistemi di Supporto Decisionale: se vedo che una filiale vende, ad es., più pomodori è logico che me ne chieda il perché per estendere questo vantaggio anche alle altre filiali: ecco il sunto del GeoMarketing.

Uno dei sistemi GIS Free and Open-Source più blasonati è sicuramente QGIS.

Se andate a guardare il file CSV che abbiamo generato vi accorgerete che, grazie a MaxMind, adesso abbiamo Latitudine e Longitudine tra i campi 😉 .

Installiamo QGIS

Beh… in verità non c’è molto da fare: basta andare su https://www.qgis.org/it/site/forusers/download.html e scaricare la versione di nostro interesse; per motivi che vanno decisamente oltre questo tutorial, Vi suggerisco di scaricare non l’ultima versione ma la versione LTR (è più stabile e ha un ciclo di vita più lungo); oltre che per Windows, Qgis è disponibile anche per MacOS e per varie distro Linux (e qui lascio la parola all’ottima documentazione presente sul sito).

Se avete scelto l’installer di base (e non OSGeo4W) basterà il classico doppio click e parte l’installazione; a installazione finita troverete sul dektop una cartella contenente i link per avviare le varie parti del software: QGIS è un programmma immenso e (al momento in cui sto scrivendo) ha a disposizione più di 1.500 plugin che ne estendono le funzionalità in vari ambiti.

Cosa ce ne facciamo?

Come facevo notare prima, grazie a MaxMind, adesso abbiamo Latitudine, Longitudine, Stato, Città e Codice Postale di ogni indirizzo IP che abbiamo dato in pasto allo script.

Come noterete all’apertura di QGIS, esso è in grado di acquisire dati da una moltitudine di sorgenti (inclusi i file CSV)… quindi, semplicemente, andremo a importare il CSV appena generato per avere una visione geografica dei visitatori.

Siccome ogni tanto un’immagine ci sta, penso che non ci sia niente di male a farVi vedere il risultato finale ottenuto georeferenziando i miei log.

Occhio: i “pallini” rappresentano le visite ma non ho applicato un sistema per farli “più grandi” in base al numero di visite da quel luogo.

Come lo facciamo?

Allora: abbiamo detto che, a installazione di QGIS terminata, Vi ritrovate con una cartella sul desktop; apritela e cliccate su QGIS Desktop 3.34.5 (il numero di versione può ovviamente variare) e lasciamo partire il programma (è pesantino e ci vuole un po’).

Installiamo un plugin per Google Maps

Come Vi sarete accorti il sistema, oltre ad un mare di menù e icone, è bianco: iniziamo col dire che QGIS, per quanto ci provi, è veramente un’enormità di software (aka: ci vuole un po’ per prenderci confidenza).

Dal menù “Plugins“, scegliamo la prima voce “Gestisci ed Installa Plugins“.

Si aprirà una finestra; nella barra di ricerca scrivete: HCMGIS (che permette di mettere uno “sfondo” di Google maps, tra le altre cose) e clicchiamo su Installa.

Vi dovrebbe essere spuntato un nuovo menù che si chiama come il plugin (HCMGIS); andiamo lì e, dalla prima voce di menù (“Basemaps“) scegliamo “Google Satellite Hybrid“.

A questo punto, tempo di download permettendo, Vi dovrebbe essere apparsa la mappa del globo come da Google Maps; noi abbiamo scelto la versione Hybrid e quindi avremo anche le etichette dei vari luoghi.

A questo punto abbiamo speso vari Gigabyte di spazio sull’Hard Disk per ottenere un visualizzatore di Google Maps 😉 .

Importiamo il CSV

Adesso è il momento di sovrapporre il CSV che abbiamo generato tramite lo script.

Andiamo nel menù “Layer” su “Aggiungi Layer” e scegliamo “Aggiungi Layer Testo Delimitato” (come potete notare vengono accettati dati da moltissime sorgenti di dati).

Si aprirà una finestra e, nell’ordine, andiamo a svolgere le seguenti operazioni:

  1. Nel primo campo (Nome File) andiamo a selezionare il CSV che abbiamo generato con lo script
  2. In “Definizione della Geometria” selezionate come Campo X il valore longitude del CSV e come Campo Y il valore latitude (la famosa georeferenziazione)
  3. Come Campo Z (che potrebbe essere: l’altezza di un luogo come le quote di pioggia o persino il numero di articoli venduti) per adesso mettete ip (ne riparliamo tra poco)
  4. Accertatevi che il campo “SR della Geometria” (il sistema di riferimento) sia: EPSG:4326 – WGS 84

Et voilà… se tutto è andato bene avete anche voi una mappa simile a quella che ho presentato poco sopra.

I punti 1 e 2 credo che siano abbastanza autoesplicativi; per il punto 3 ne parliamo dopo e per il punto 4 sorvoliamo elegantemente (Vi basti sapere che, quando ho studiato i sistemi di riferimento all’università mi stavano venendo leggermente i capelli bianchi… per fortuna le cose sono andate parecchio avanti e qui ci limitiamo a usare il software).

Note conclusive

Cosa potrebbe andare storto?

Se avete avuto problemi, innanzitutto dovete dare un’occhiata al formato dei log: come scrivevo io utilizzo WordOps ma, se devo essere onesto, ho anche utilizzato l’ottimo GPT-4 per scrivere la RegEx.

Se avete dei problemi in tal senso, mi va di condividere una mia tecnica di prompting: se/quando chiedete al buon ChatGPT di correggere la RegEx (alla riga 9 dello script) aggiungete alcune righe di log alla query, in maniera tale che il ChatBot possa usufruirne.

Come potremmo migliorare il tutto?

Qui il discorso si allunga: come avrete notato in questo script non vengono contate le visite da ogni indirizzo IP e quindi, poi bisogna giocare di QGIS per creare una mappa con pallini più grandi per un numero di visite maggiore.

Anche latitudine e longitudine: alle volte, sebbene l’indirizzo IP sia diverso, hanno le stesse coordinate geografiche; bisognerebbe anche utilizzare la Network (mai l’investimento nella CCNA R&S fu più utile 🙂 ; tramite il metodo response.traits.network del pacchetto geoip2 di MaxMind).

L’ideale sarebbe caricare tutto in un database (QGIS supporta molto bene PostgreSQL con l’estensione per le geometrie spaziali PostGIS, comodamente installabile tramite Docker).

L’ideale sarebbe scrivere un plugin per QGIS in maniera tale da automatizzare il tutto: non è impossibile che, tempo permettendo, inizi a smanettarci un po’.

Conclusioni

Scrivere questo pezzo è stato stancante ma anche sfidante: spero che possa tornare utile anche a Voi.

Ovviamente, se avete dei problemi (o anche solo per dirmi che Vi è piaciuto) Vi invito a commentare il pezzo in maniera tale che possiamo provare a risolvere eventuali problemi 🙂 .


Commenti

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.