src/public/Logging/Export-AitherErrorReport.ps1
|
#Requires -Version 7.0 <# .SYNOPSIS Generate comprehensive error reports for debugging and support .DESCRIPTION Generates detailed error reports in multiple formats (JSON, HTML, Text) suitable for debugging, support tickets, or documentation. Includes all context, stack traces, parameters, and environment information needed to diagnose issues. .PARAMETER Since Include errors since this date/time. Default is last 24 hours. .PARAMETER Until Include errors until this date/time. .PARAMETER Cmdlet Filter errors by cmdlet name(s). .PARAMETER OutputPath Path where the error report will be saved. Default is library/logs/error-reports/ directory. .PARAMETER Format Report format: JSON (machine-readable), HTML (human-readable), Text (plain text), or All (all formats). .PARAMETER IncludeEnvironment Include environment information (PowerShell version, OS, modules, etc.) in the report. .PARAMETER IncludeStackTraces Include full stack traces in the report. Useful for deep debugging. .INPUTS System.String You can pipe cmdlet names to Export-AitherErrorReport. .OUTPUTS System.String Returns the path to the generated report file(s). .EXAMPLE Export-AitherErrorReport Generates an error report for the last 24 hours in JSON format. .EXAMPLE Export-AitherErrorReport -Since (Get-Date).AddDays(-7) -Format HTML Generates an HTML report for the last week. .EXAMPLE Export-AitherErrorReport -Cmdlet "Invoke-AitherScript" -Format All -IncludeStackTraces Generates all format reports for Invoke-AitherScript errors with stack traces. .EXAMPLE Export-AitherErrorReport -OutputPath "C:\Reports\errors.json" -Format JSON Generates a JSON report in a custom location. .NOTES Error reports are comprehensive and include: - All error details (message, exception, stack trace) - Parameters that were passed - Environment information - System configuration - Timeline of errors Use these reports for: - Submitting bug reports - Internal debugging - Documentation of issues - Performance analysis .LINK Get-AitherErrorLog Write-AitherLog #> function Export-AitherErrorReport { [OutputType([System.String])] [CmdletBinding(SupportsShouldProcess)] param( [Parameter()] [DateTime]$Since = (Get-Date).AddDays(-1), [Parameter()] [DateTime]$Until, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [string[]]$Cmdlet, [Parameter()] [string]$OutputPath, [Parameter()] [ValidateSet('JSON', 'HTML', 'Text', 'All')] [string]$Format = 'JSON', [switch]$IncludeEnvironment, [switch]$IncludeStackTraces, [switch]$ShowOutput ) begin { # Save original log targets $originalLogTargets = $script:AitherLogTargets # Set log targets based on ShowOutput parameter if ($ShowOutput) { # Ensure Console is in the log targets if ($script:AitherLogTargets -notcontains 'Console') { $script:AitherLogTargets += 'Console' } } else { # Remove Console from log targets if present (default behavior) if ($script:AitherLogTargets -contains 'Console') { $script:AitherLogTargets = $script:AitherLogTargets | Where-Object { $_ -ne 'Console' } } } $moduleRoot = Get-AitherModuleRoot if (-not $OutputPath) { $OutputPath = Join-Path $moduleRoot 'AitherZero/library/logs' 'error-reports' } if (-not (Test-Path $OutputPath)) { New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null } $timestamp = Get-Date -Format 'yyyy-MM-dd_HHmmss' $errors = @() } process { try { # During module validation, skip execution if ($PSCmdlet.MyInvocation.InvocationName -eq '.') { return } # Check if Write-AitherLog is available $hasWriteAitherLog = Get-Command Write-AitherLog -ErrorAction SilentlyContinue # Collect errors $errorParams = @{ Since = $Since Count = -1 } if ($Until) { $errorParams.Until = $Until } if ($Cmdlet) { $errorParams.Cmdlet = $Cmdlet } $errors = Get-AitherErrorLog @errorParams } catch { if ($hasWriteAitherLog) { Write-AitherLog -Level Warning -Message "Error collecting errors for report: $($_.Exception.Message)" -Source $PSCmdlet.MyInvocation.MyCommand.Name } else { Write-AitherLog -Level Warning -Message "Error collecting errors for report: $($_.Exception.Message)" -Source 'Export-AitherErrorReport' -Exception $_ } } } end { try { try { $reportData = @{ ReportGenerated = Get-Date ReportPeriod = @{ Since = $Since Until = if ($Until) { $Until } else { Get-Date } } TotalErrors = $errors.Count Errors = $errors } if ($IncludeEnvironment) { $reportData.Environment = @{ PowerShellVersion = $PSVersionTable.PSVersion.ToString() OS = if ($IsWindows) { "Windows" } elseif ($IsLinux) { "Linux" } elseif ($IsMacOS) { "macOS" } else { "Unknown" } ComputerName = if ($env:COMPUTERNAME) { $env:COMPUTERNAME } else { $env:HOSTNAME } UserName = if ($IsWindows) { [System.Security.Principal.WindowsIdentity]::GetCurrent().Name } else { $env:USER } ModuleVersion = (Get-Module AitherZero -ErrorAction SilentlyContinue).Version.ToString() } } $generatedFiles = @() # Generate JSON report if ($Format -in @('JSON', 'All')) { $jsonFile = Join-Path $OutputPath "error-report-${timestamp}.json" if ($PSCmdlet.ShouldProcess($jsonFile, "Generate JSON error report")) { $reportData | ConvertTo-Json -Depth 10 | Out-File -FilePath $jsonFile -Encoding UTF8 -Force $generatedFiles += $jsonFile if ($hasWriteAitherLog) { Write-AitherLog -Level Information -Message "Generated JSON error report: $jsonFile" -Source $PSCmdlet.MyInvocation.MyCommand.Name } } } # Generate HTML report if ($Format -in @('HTML', 'All')) { $htmlFile = Join-Path $OutputPath "error-report-${timestamp}.html" if ($PSCmdlet.ShouldProcess($htmlFile, "Generate HTML error report")) { $html = @" <!DOCTYPE html> <html> <head> <title>AitherZero Error Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } h1 { color: #d32f2f; } .error { border: 1px solid #ccc; margin: 10px 0; padding: 10px; background: #fff3cd; } .error-header { font-weight: bold; color: #d32f2f; } .timestamp { color: #666; font-size: 0.9em; } pre { background: #f5f5f5; padding: 10px; overflow-x: auto; } </style> </head> <body> <h1>AitherZero Error Report</h1> <p><strong>Generated:</strong> $($reportData.ReportGenerated)</p> <p><strong>Period:</strong> $($reportData.ReportPeriod.Since) to $($reportData.ReportPeriod.Until)</p> <p><strong>Total Errors:</strong> $($reportData.TotalErrors)</p> $(if ($IncludeEnvironment) { "<h2>Environment</h2><pre>$($reportData.Environment | ConvertTo-Json -Depth 10)</pre>" }) <h2>Errors</h2> $(foreach ($error in $errors) { $stackTrace = if ($IncludeStackTraces -and $error.StackTrace) { "<pre>$($error.StackTrace)</pre>" } else { "" } @" <div class="error"> <div class="error-header">$($error.Cmdlet) - $($error.Message)</div> <div class="timestamp">$($error.Timestamp)</div> <p><strong>Error ID:</strong> $($error.ErrorId)</p> $(if ($error.Parameters) { "<p><strong>Parameters:</strong> <pre>$($error.Parameters | ConvertTo-Json -Compress)</pre></p>" }) $stackTrace </div> "@ }) </body> </html> "@ $html | Out-File -FilePath $htmlFile -Encoding UTF8 -Force $generatedFiles += $htmlFile if ($hasWriteAitherLog) { Write-AitherLog -Level Information -Message "Generated HTML error report: $htmlFile" -Source $PSCmdlet.MyInvocation.MyCommand.Name } } } # Generate Text report if ($Format -in @('Text', 'All')) { $textFile = Join-Path $OutputPath "error-report-${timestamp}.txt" if ($PSCmdlet.ShouldProcess($textFile, "Generate text error report")) { $text = @" AitherZero Error Report ======================== Generated: $($reportData.ReportGenerated) Period: $($reportData.ReportPeriod.Since) to $($reportData.ReportPeriod.Until) Total Errors: $($reportData.TotalErrors) $(if ($IncludeEnvironment) { "Environment Information: $(($reportData.Environment | ConvertTo-Json -Depth 10)) " }) Errors: ------- $(foreach ($error in $errors) { @" [$($error.Timestamp)] $($error.Cmdlet) Error ID: $($error.ErrorId) Message: $($error.Message) $(if ($error.Parameters) { "Parameters: $($error.Parameters | ConvertTo-Json -Compress)`n " }) $(if ($IncludeStackTraces -and $error.StackTrace) { "Stack Trace:`n $($error.StackTrace -replace "`n", "`n ")`n " }) "@ }) "@ $text | Out-File -FilePath $textFile -Encoding UTF8 -Force $generatedFiles += $textFile if ($hasWriteAitherLog) { Write-AitherLog -Level Information -Message "Generated text error report: $textFile" -Source $PSCmdlet.MyInvocation.MyCommand.Name } } }return $generatedFiles } catch { # Use centralized error handling $errorScript = Join-Path $PSScriptRoot '..' 'Private' 'Write-AitherError.ps1' if (Test-Path $errorScript) { . $errorScript -ErrorRecord $_ -CmdletName $PSCmdlet.MyInvocation.MyCommand.Name -Operation "Generating error report" -Parameters $PSBoundParameters -ThrowOnError } else { $errorObject = [PSCustomObject]@{ PSTypeName = 'AitherZero.Error' Success = $false ErrorId = [System.Guid]::NewGuid().ToString() Cmdlet = $PSCmdlet.MyInvocation.MyCommand.Name Operation = "Generating error report" Error = $_.Exception.Message Timestamp = Get-Date } Write-Output $errorObject if ($hasWriteAitherLog) { Write-AitherLog -Level Error -Message "Failed to generate error report: $($_.Exception.Message)" -Source $PSCmdlet.MyInvocation.MyCommand.Name -Exception $_ } else { Write-AitherLog -Level Error -Message "Failed to generate error report: $($_.Exception.Message)" -Source 'Export-AitherErrorReport' -Exception $_ } } throw } } finally { # Restore original log targets $script:AitherLogTargets = $originalLogTargets } } } |