XinLog 1.2 – Controlliamo i livelli di verbosità

Una delle feature più importanti quando si usa logga è la possibilità di definire il tipo di log che si sta scrivendo, questo perchè oltre ad essere una info assai importante per chi legge il log stesso ci consente anche di definire ad un livello più alto che dettaglio voglio avere nei stessi. L’esempio classico è quello della fase di sviluppo: normalmente quando si sta scrivendo un nuovo codice o script si ha necessità di avere un livello di log molto altro perchè serve per debuggare, ossia analizzare che cosa lo script ha eseguito passo-passo e magari magari il perché di un malfunzionamento o un evento inatteso. Al contrario, normalmente quando uno script è già stato utilizzato diverse volte non è necessario avere un dettaglio troppo elevato ma solo i messaggi più critici e le eccezioni. Detto questo per il nostro Log definiamo tre livelli principali:

  • Critical: per i problemi critici che hanno impatto nell’esecuzione dello script ad esempio gli errori
  • Warning: per problemi che non bloccano l’esecuzione ma per i quali è importante evidenziare l’esistenza
  • Info: per tutto ciò che può essere una info, ad esempio note per il debug
    Param (
        [Parameter(Mandatory)]    
        [string]$LogString,
        [ValidateSet("Info", "Warning", "Critical")]
        [string]$Level="Info"
    )

Aggiungiamo alla Write-Log il parametro $Level che definiremo tramite la funzione ValidateSet un LOV (List-of-Values) in modo che possano essere specificati solo i tre valori sopra e come valore di default imposto “Info” cosicchè se non specificato consideri il livello minore. A questo punto si tratta di modficare il log per aggiungere l’informazione del livello e dato che potrebbe essere utile definire anche un colore per la visualizzazione a schermo.

        $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
        $LogMessage = "$Stamp - $Level - $LogString"
        if($Level -eq "Info"){
            Add-content $global:_Logfile -value $LogMessage
            Write-Host $LogString -ForegroundColor White
        }
        elseif($Level -eq "Warning"){
            Add-content $global:_Logfile -value $LogMessage
            Write-Host $LogString -ForegroundColor Yellow
        }
        elseif($Level -eq "Critical"){
            Add-content $global:_Logfile -value $LogMessage
            Write-Host $LogString -ForegroundColor Red
        }

Ora che abbiamo modificato il log non ci resta che fare un test e verificare come cambia il log a seconda della tipologia che abbiamo scelto. Proviamo dunque con il test seguente:

Test d’utilizzo
Risultato a schermo
Risultato nei log

Come si può notare, se non specifichiamo il livello, di default viene interpretato come Info. Importante notare come un livello sconosciuto alzi un errore nel log, mentre il livello non è case sensitive.

Facciamo ora un altro step, normalmente quando si utilizzano i log è utile definire dall’applicazione che li utilizza che livello si vuole loggare in modo da evitare un livello troppo “verboso” qualora non sia necessario. Per farlo aggiungiamo nella Open-Log la possibilità di definire il livello di log

function Open-Log{
    Param (   
        [string]$StoragePath,
        [ValidateSet("Info", "Warning", "Critical")]
        [string]$Level="Info"
    )
    try{
        $global:_Level = $Level
        if($StoragePath -ne $null){
       ...

Naturalmente come fatto in precedenza memorizziamo l’informazione sul livello scelto in una variabile globale di modo che resti disponibile durante tutto il processo. A questo punto si tratta di aggiungere il check all’interno della Write-Log che scriverà solo ciò che rispetta il criterio di log definito nella Open-Log.

        $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
        $LogMessage = "$Stamp - $Level - $LogString"
        if(($Level -eq "Info") -and ($global:_Level -eq "Info")){
            Add-content $LogFile -value $LogMessage
            Write-Host $LogString -ForegroundColor White
        }
        elseif(($Level -eq "Warning") -and (($global:_Level -eq "Warning") -or ($global:_Level -eq "Info"))){
            Add-content $LogFile -value $LogMessage
            Write-Host $LogString -ForegroundColor Yellow
        }
        elseif($Level -eq "Critical"){
            Add-content $LogFile -value $LogMessage
            Write-Host $LogString -ForegroundColor Red
        }

Ora se facciamo girare lo stesso test di prima definendo come livello minimo Warning tutte le Info non verranno riportate come voluto

Test con livello di verbosità su Warning

Bene, direi che, almeno per il momento il livello di flessibilità raggiunto dallo XinLog dovrebbe essere sufficiente. Se volete scaricare tutto il sorgente lo trovate qui [1].

[1] https://github.com/stepperxin/XinLog

XinLog 1.1 – Rendiamolo configurabile

Ormai ci ho preso gusto dopo le ultime puntate continuo a lavorare sul migliorare lo script di Log. Per cominciare vorrei dare l’opportunità a chi lo utilizza di decidere dove loggare. Come ricordate infatti al momento tutti i logs finiscono nella stessa cartella in cui c’è lo script e questo è limitante. Sarebbe figo invece che chi lo usa possa decidere di volta in volta dove loggare. Creeremo dunque una funzione Open-Log a cui dire come inizializzare dei parametri che poi si applichino.

# Get the current Directory
$_StoragePath = Split-Path -Parent $MyInvocation.MyCommand.Path
#Set the file log name
$_Logfile = "_StoragePath\XinLog_$($env:computername)_$((Get-Date).toString("yyyyMMdd_HHmmss")).log"

function Open-Log{
    Param (   
        [string]$StoragePath
    )
    #set the folder name
    $_StoragePath = $StoragePath
    #Set the file log name
    $_Logfile = "$_StoragePath\XinLog_$($env:computername)_$((Get-Date).toString("yyyyMMdd_HHmmss")).log"
}

Se invochiamo quindi questa Open-Log prima di utilizzarla, l’idea è che si inizializzi la variabile $_StoragePath e da lì in poi essa venga richiamata ogni volta nella Write-Log. Se però lo fate scoprirete che non funziona. Perchè? In pratica per come sono dichiarate le variabili sopra hanno un contesto limitato allo script che quindi cessa di esistere aldifuori del fil XinLog. Questo ovviamente non ci piace perchè se dobbiamo inizializzare ogni volta il file tutto perde di senso.

E’ il caso invece di giocare un pochino con le variabili e qui [1] trovate un articolo interessante in merito. E’ il caso che dichiari Global queste variabili così da riuscire a mantenere il valore nel contesto del thread lanciato e non solo del singolo file XinLog: Funziona!

# Get the current Directory
$global:_StoragePath = Split-Path -Parent $MyInvocation.MyCommand.Path
#Set the file log name
$global:_Logfile = "$global:_StoragePath\XinLog_$($env:computername)_$((Get-Date).toString("yyyyMMdd_HHmmss")).log"

function Open-Log{
    Param (   
        [string]$StoragePath
    )
    #set the folder name
    $global:_StoragePath = $StoragePath
    #Set the file log name
    $global:_Logfile = "$global:_StoragePath\XinLog_$($env:computername)_$((Get-Date).toString("yyyyMMdd_HHmmss")).log"
}

Come potete ben vedere l’unica accortezza è di utilizzare il prefisso $global: per tutte le variabili che vogliamo mantengano un valore che esuli dal singolo contesto di File e persistano anche nel Thread.

Infine aggiungiamo un po’ di try-catch per evitare che log (utilizzato anche per loggare gli errori) possa a sua volta bloccarsi per qualche eccezzione inattesa ed il gioco è fatto: la versione XinLog 1.1 è servita (GitHub [2])

[1] https://www.varonis.com/blog/powershell-variable-scope

[2] https://github.com/stepperxin/XinLog

XinLog 1.0

Dopo aver cominciato il thread [1] delle scorse settimane, ho pensato fosse opportuno spendere qualche minuto in più per produrre una libreria separata per il logging con l’opportunità che essa possa essere riutilizzata anche in altri contesti. Essendo infatti il logging concettualmente un’azione riutilizzabile più e più volte in mille contesti distinti sarebbe bello che questa funzionalità stesse in un file separato e che fosse richiamabile semplicemente includendola nel contesto in cui mi serve.

Per questa ragione creo un un file ps1 separato che chiamerò XinLog in cui isolerò tutte le logiche relative al logging:

################# XinLog 1.0 ######################
# Use this library to log easely on file and screen
# In Parameters MANDATORY
#   - $LogString [STRING] Message to log
# The log on file will create file a file in the same directory of the caller
###################################################

# Get the current Directory
$myDir = Split-Path -Parent $MyInvocation.MyCommand.Path
#Set the file log name
$Logfile = "$myDir\XinLog_$($env:computername)_$((Get-Date).toString("yyyyMMdd_HHmmss")).log"

#begin FUNCTIONS
function Write-Log
{
    Param (
        [Parameter(Mandatory)]    
        [string]$LogString
    )
    $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
    $LogMessage = "$Stamp - $LogString"
    Add-content $LogFile -value $LogMessage
    Write-Host $LogString -ForegroundColor White
}

#end FUNCTIONS

Non ho fatto granchè in realtà, ho semplicemente esportato le righe relative al logging dal file precedente così che il file contenga solo la funzione Write-Log (che scrive effettivamente il log) ed i settaggi che servono ad indivuare dove scrivere il log. Al momento questi settaggi non sono modificabili, ma chissà, in futuro…

A questo punto però come posso utilizzare la Write-Log se sta in un file separato? La risposta è ovviamente semplicissima, come in tutti i linguaggi è possibile includere uno script in un altro script, nella fattispecie in PowerShell questo si fa con la “dot source notation” descritta qui [2] in cui basta sostanzialmente includere il il file con il path specifico. Nel nostro caso il file Conto2.3 cambierà nel modo seguente:

########### CONTO 2.3.1 ############
# First test
####################################

##### Inclueded Libraries ##########
# Get the current Directory
$myDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# Files included 
. "$myDir\XinLog.ps1"  # Include the file logs
####################################


#begin BODY

Write-Log "Text to write"

#end BODY

Per chi fosse interessato potete trovare anche uno progetto Git dedicato [3].

[1] https://www.beren.it/2022/01/07/conto2-3-logging-with-powershell-get-started/

[2] https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes?view=powershell-5.1#using-dot-source-notation-with-scope

[3] https://github.com/stepperxin/XinLog