Public/Test-FDAObservability.ps1
|
function Test-FDAObservability { <# .SYNOPSIS Health check. Verifies connectivity, schema, ingestion path, retention, and recent activity. .DESCRIPTION Returns one object per check with Status (Pass/Fail/Warning) and a Detail message. Designed to be wired into a monitoring system. .EXAMPLE Test-FDAObservability | Format-Table -AutoSize .EXAMPLE if ((Test-FDAObservability).Where({$_.Status -ne 'Pass'})) { 'unhealthy' } #> [CmdletBinding()] param() $results = [System.Collections.Generic.List[object]]::new() function Add-Result($name, $status, $detail) { $results.Add([pscustomobject]@{ Check = $name; Status = $status; Detail = $detail }) } # 1. Connection if (-not $script:FDAState.Connected) { Add-Result 'Connection' 'Fail' 'Not connected. Call Connect-FDAObservability first.' return $results } Add-Result 'Connection' 'Pass' ("AuthMethod={0}, Workspace={1}" -f $script:FDAState.AuthMethod, $script:FDAState.WorkspaceId) # 2. Token fetch try { Get-FDAAccessToken -Scope 'https://kusto.fabric.microsoft.com/.default' | Out-Null Add-Result 'Token (Kusto)' 'Pass' 'Token acquired.' } catch { Add-Result 'Token (Kusto)' 'Fail' $_.Exception.Message } # 3. Cluster query reachable try { Invoke-KQLQuery -Query 'print x=1' | Out-Null Add-Result 'Cluster Query' 'Pass' 'KQL roundtrip OK.' } catch { Add-Result 'Cluster Query' 'Fail' $_.Exception.Message } # 4. Tables present $expectedTables = @( 'FDAInteractions', 'FDAInteractionsRaw', 'FDAExecutions', 'FDAExecutionsRaw', 'FDAAuthEvents', 'FDAAuthEventsRaw', 'FDACostMetering', 'FDACostMeteringRaw', 'FDALogEvents', 'FDALogEventsRaw', 'FDALogLevels', 'FDAConfiguration' ) try { $rows = Invoke-KQLQuery -Query '.show tables | project TableName' $found = @($rows | ForEach-Object { $_.TableName }) $missing = $expectedTables | Where-Object { $_ -notin $found } if ($missing.Count -eq 0) { Add-Result 'Schema' 'Pass' 'All curated, raw, and operational tables present.' } else { Add-Result 'Schema' 'Fail' ('Missing tables: ' + ($missing -join ', ')) } } catch { Add-Result 'Schema' 'Warning' "Could not inspect tables: $($_.Exception.Message)" } # 5. Log levels seeded try { $lvls = Invoke-KQLQuery -Query 'GetActiveLogLevels() | count' $count = if ($lvls -and $lvls[0]) { [int]$lvls[0].Count } else { 0 } if ($count -ge 5) { Add-Result 'Log levels seeded' 'Pass' "$count active levels." } else { Add-Result 'Log levels seeded' 'Warning' "$count active levels (expected >= 5)." } } catch { Add-Result 'Log levels seeded' 'Warning' $_.Exception.Message } # 6. Roundtrip ingest (synthetic event) try { $eventId = [guid]::NewGuid().ToString() Write-FDALog -Level Information -Message "Test-FDAObservability ping $eventId" -Category 'HealthCheck' -Properties @{ Marker = $eventId } Invoke-FDAFlush Start-Sleep -Seconds 2 $hit = Invoke-KQLQuery -Query "FDALogEvents | where Properties.Marker == '$eventId' | count" $found = if ($hit -and $hit[0]) { [int]$hit[0].Count } else { 0 } if ($found -ge 1) { Add-Result 'Round-trip ingest' 'Pass' "Marker $eventId visible." } else { Add-Result 'Round-trip ingest' 'Warning' 'Marker not visible yet (ingest in flight).' } } catch { Add-Result 'Round-trip ingest' 'Fail' $_.Exception.Message } # 7. Spool drain status $spool = Get-ChildItem -Path $script:FDAState.SpoolPath -Filter '*.spool.json' -ErrorAction SilentlyContinue if (-not $spool -or $spool.Count -eq 0) { Add-Result 'Spool empty' 'Pass' 'No spooled events awaiting drain.' } else { Add-Result 'Spool empty' 'Warning' ('{0} spooled file(s); will drain on next ingest.' -f $spool.Count) } return $results } |