Vor ein paar Wochen habe ich ein Update der Veeam Backup & Replication Umgebung in meinem Lab durchgeführt. Um nach dem Update sicherzustellen, dass auch wirklich alles ohne Probleme funktioniert hat, habe ich begonnen alle Windows Eventlogs und Log Files zu analysieren. Schnell hat sicher aber herausgestellt, ohne ein passendes Tools ist die Auswertung der Veeam log Files viel zu aufwendig. Da mein letztes öffentliches PowerShell Projekt bereits ein paar Monate zurück lag (und ich unbedingt einen Grund gesucht habe mit PowerShell Klassen zu arbeiten), habe ich meine Funktion zur Veeam Log File Analyse mit PowerShell begonnen.
Veeam Log File Analyse
Alle Veeam log files sind in der Standardkonfiguration unter ‚C:\ProgramData\Veeam\<Produkt>‘ abgelegt. Die meisten relevanten Veeam Services bzw. Produkte legen dort einen Unterordner an und erzeugen meist mehrere Log Files wie zum Beispiel ‚Svc.VeeamEndpointBackup.log‘ (Veeam Agent for Microsoft Windows).
Die meisten von mir analysierten Log Files halten sich an folgendes Schema für den Hauptteil:
[23.11.2016 23:07:16] <01> Info ------- Veeam Endpoint Job Started -------
Die Analyse mit PowerShell
PowerShell ist ein super Hilfswerkzeug um schnell Daten aus Test Files zu extrahieren. Besonders gut funktioniert das, wenn die gewünschten Informationen in bestimmten Schemen (RegEx Pattern) vorliegen:
$Content = Get-Content -Path "C:\ProgramData\Veeam\Endpoint\Svc.VeeamEndpointBackup.log" $Content | Select-String -Pattern "\[\d+.\d+.\d+\s\d+\:\d+:\d+]\s\<\d+\>\sError"
Diese zwei kurzen Zeilen geben alle Error Meldungen aus dem Veeam Agent for Microsoft Windows Log File aus.
Diese Befehle aber nun für jedes einzelne Veeam Log File durchzugehen wäre nicht sehr komfortabel, daher habe ich beschlossen ein PowerShell Modul mit einer Funktion zur Veeam Log File Analyse zu erstellen. Die Funktion sollte als Input nur das gewünschte Log File, welches analysiert werden soll, und ein paar Ausgabeoptionen haben.
Dieses Beispiel zeigt die Ausgabe aller in der Funktion vorgesehener Log Files. Die Darstellung ist limitiert auf die letzte Error oder Warning Meldung pro Log File:
Wenn der optionale Parameter -Context 2 genutzt wird, werden alle Meldungen im Kontext ausgegeben. Die Meldungen selbst sind in der Ausgabe mit einem > gekennzeichnet:
Hauptfunktion des PowerShell Moduls
function Invoke-VeeamLogParser { <# .DESCRIPTION The Veeam Log Parser Function extracts Error and Warning Messages from the Veeam File Logs of various products and services. .NOTES File Name : Invoke-VeeamLogParser.psm1 Author : Markus Kraus Version : 1.0 State : Ready .LINK https://mycloudrevolution.com/ .EXAMPLE Invoke-VeeamLogParser -LogType Endpoint -Limit 2 .EXAMPLE Invoke-VeeamLogParser -LogType Endpoint -Limit 2 -Context 2 .PARAMETER VeeamBasePath The Base Path of the Veeam Log Files Default: "C:\ProgramData\Veeam\" .PARAMETER Context Show messages in Context .PARAMETER Limit Show limited number of messages .PARAMETER LogType The products or services Log you want to show Valid Pattern: "All","Endpoint","Mount","Backup","EnterpriseServer","Broker","Catalog","RestAPI","BackupManager", "CatalogReplication","DatabaseMaintenance","WebApp","PowerShell" #> [CmdletBinding()] param( [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="The Base Path of the Veeam Log Files")] [ValidateNotNullorEmpty()] [String] $VeeamBasePath = "C:\ProgramData\Veeam\", [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="Show messages in Context")] [ValidateNotNullorEmpty()] [int]$Context, [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="Show limited number of messages")] [ValidateNotNullorEmpty()] [Int]$Limit, [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="The products or services Log you want to show")] [ValidateNotNullorEmpty()] [ValidateSet("All","Endpoint","Mount","Backup","EnterpriseServer","Broker","Catalog","RestAPI","BackupManager", "CatalogReplication","DatabaseMaintenance","WebApp","PowerShell")] [String]$LogType ) Begin { class LogParser { #Properties [String]$Name [String]$BasePath [String]$Folder [String]$File #Static Static [String] $WarningPattern = "\[\d+.\d+.\d+\s\d+\:\d+:\d+]\s\<\d+\>\sWarning" Static [String] $ErrorPattern = "\[\d+.\d+.\d+\s\d+\:\d+:\d+]\s\<\d+\>\sError" #Constructor LogParser ([String] $Name, [String]$BasePath, [String]$Folder, [String]$File) { $this.Name = $Name $this.BasePath = $BasePath $this.Folder = $Folder $this.File = $File } #Method [Bool]checkFolder() { return Test-Path $($this.BasePath + $this.Folder) } #Method [Bool]checkFile() { return Test-Path $($this.BasePath + $this.Folder + "\" + $this.File) } #Method [Array]getContent() { if ($this.checkFolder()) { if ($this.checkFile()) { return Get-Content $($this.BasePath + $this.Folder + "\" + $this.File) } else { return Write-Warning "File not found" } } else { return Write-Warning "Folder not found" } } #Method [Array]getErrors() { $Content = $this.getContent() return $Content | Select-String -Pattern $([LogParser]::ErrorPattern) -AllMatches } #Method [Array]getErrors([int]$ContentB, [int]$ContentA) { $Content = $this.getContent() return $Content | Select-String -Pattern $([LogParser]::ErrorPattern) -AllMatches -Context $ContentB, $ContentA } #Method [Array]getWarnings() { $Content = $this.getContent() return $Content | Select-String -Pattern $([LogParser]::WarningPattern) -AllMatches } #Method [Array]getWarnings([int]$ContentB, [int]$ContentA) { $Content = $this.getContent() return $Content | Select-String -Pattern $([LogParser]::WarningPattern) -AllMatches -Context $ContentB, $ContentA } #Method [Array]getErrorsAndWarnings() { $Content = $this.getContent() return $Content | Select-String -Pattern $([LogParser]::ErrorPattern), $([LogParser]::WarningPattern) -AllMatches } #Method [Array]getErrorsAndWarnings([int]$ContentB, [int]$ContentA) { $Content = $this.getContent() return $Content | Select-String -Pattern $([LogParser]::ErrorPattern), $([LogParser]::WarningPattern) -AllMatches -Context $ContentB, $ContentA } } function Invoke-Output ($item) { if ($Context) { $Select = $item.getErrorsAndWarnings($Context, $Context) } else { $Select = $item.getErrorsAndWarnings() } if ($Limit) { $Select | Select-Object -Last $Limit } else { $Select } } $LogTypes = @() $LogTypes += [LogParser]::new("Endpoint", $VeeamBasePath, "Endpoint", "Svc.VeeamEndpointBackup.log") $LogTypes += [LogParser]::new("Mount", $VeeamBasePath, "Backup", "Svc.VeeamMount.log") $LogTypes += [LogParser]::new("Backup", $VeeamBasePath, "Backup", "Svc.VeeamBackup.log") $LogTypes += [LogParser]::new("EnterpriseServer", $VeeamBasePath, "Backup", "Svc.VeeamBES.log") $LogTypes += [LogParser]::new("Broker", $VeeamBasePath, "Backup", "Svc.VeeamBroker.log") $LogTypes += [LogParser]::new("Catalog", $VeeamBasePath, "Backup", "Svc.VeeamCatalog.log") $LogTypes += [LogParser]::new("RestAPI", $VeeamBasePath, "Backup", "Svc.VeeamRestAPI.log") $LogTypes += [LogParser]::new("BackupManager", $VeeamBasePath, "Backup", "VeeamBackupManager.log") $LogTypes += [LogParser]::new("CatalogReplication", $VeeamBasePath, "Backup", "CatalogReplicationJob.log") $LogTypes += [LogParser]::new("DatabaseMaintenance", $VeeamBasePath, "Backup", "Job.DatabaseMaintenance.log") $LogTypes += [LogParser]::new("WebApp", $VeeamBasePath, "Backup", "Veeam.WebApp.log") $LogTypes += [LogParser]::new("PowerShell", $VeeamBasePath, "Backup", "VeeamPowerShell.log") } Process { if ($LogType -eq "All") { foreach ($item in $LogTypes) { Write-Host "`nProcessing '$($item.File)' in '$($item.BasePath + $item.Folder + "\")'" -ForegroundColor Gray Invoke-Output $item } } else { $item = $LogTypes | Where-Object {$_.Name -eq $LogType } if ($item) { Write-Host "`nProcessing '$($item.File)' in '$($item.BasePath + $item.Folder + "\")'" -ForegroundColor Gray Invoke-Output $item } else { Throw "Internal Error: LogType Missmatch" } } } }
An dem PowerShell Modul mitwirken
Falls bei der Verwendung des Moduls ein Fehler auftaucht oder ein Verbesserungsvorschlag aufkommt freue ich mich über jeden Pull Request und GitHub Issue
Download des PowerShell Moduls
Das PowerShell Modul ist verfügbar auf GitHub und in der PowerShell Gallery:
VeeamLogParser – GitHub VeeamLogParser – PS Gallery
Ich kann auch sehr empfehlen, sich für Veeam Sexilog als fix und fertigen ELK-Stack (Elastisearch, Logstash, Kibana) einmal anzuschauen. Kibana bietet eine GUI mit Such- und Drilldown möglichkeiten, sowie die Möglichkeit andere Logs im gleichen zeitlichen Verlauf darzustellen. So kann man zum Beispiel Datenbankfehler im Zuge von Snapshot-Aktivitäten sauber diagnostizieren bzw. die Ursache zielsicher benennen. Sexilog ist zwar für den dauerhaften Betrieb zu instabil und mittlerweile veraltet, aber den Veeam-Log-Filter daraus und einige Skripting Teile für logstash/Elastisearch habe ich fast in allen meinen ELK-Stacks übernommen.
ich bin immer wieder erstaunt, dass es noch Kunden ohne zentrales Logging gibt.
Vielen Dank für deinen Kommentar Falk.
Ich bin absolut bei dir. Zentrales Log Management ist fast unerlässlich geworden. Ich persönlich arbeite sehr gerne mit vRealize Log Insight, da die Agenten sehr flexibel sind um alles ein zu sammeln.