From 83315cc3b82a253bdfce6b8e5796a591551dab47 Mon Sep 17 00:00:00 2001 From: PTah Date: Thu, 28 May 2026 10:20:30 +1000 Subject: [PATCH] fix: SAC 422 no spool loop, truncate title/summary, UTF-8 spool read (1.2.9-SAC) Co-authored-by: Cursor --- Login_Monitor.ps1 | 2 +- Sac-Client.ps1 | 81 ++++++++++++++++++++++++++++++++++++++++++++--- version.txt | 2 +- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index 2f23c5c..0ddda15 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -80,7 +80,7 @@ $script:MonitorLoopInitialized = $false # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). -$ScriptVersion = "1.2.8-SAC" +$ScriptVersion = "1.2.9-SAC" # Логи (все под InstallRoot) $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" diff --git a/Sac-Client.ps1 b/Sac-Client.ps1 index f519f23..a7c3b73 100644 --- a/Sac-Client.ps1 +++ b/Sac-Client.ps1 @@ -4,7 +4,7 @@ .DESCRIPTION Dot-source после login_monitor.settings.ps1 и функции Write-Log. Ожидает: $UseSAC, $SacUrl, $SacApiKey, $ScriptVersion, $script:InstallRoot. - Release: 1.2.8-SAC (ingest UTF-8 body for Cyrillic summary/details). + Release: 1.2.9-SAC (UTF-8 ingest, title/summary limits, no spool on HTTP 422). #> function Get-SacUtf8Bytes { @@ -92,6 +92,33 @@ function Get-SacCategoryForType { return 'agent' } +function Limit-SacString { + param( + [string]$Text, + [int]$MaxLen, + [string]$Label + ) + if ([string]::IsNullOrEmpty($Text)) { return '' } + if ($Text.Length -le $MaxLen) { return $Text } + Write-SacLog "WARN: SAC truncate $Label $($Text.Length) -> $MaxLen chars (event schema limit)" + return $Text.Substring(0, $MaxLen) +} + +function Get-SacHttpErrorBody { + param($Exception) + try { + if ($null -eq $Exception -or $null -eq $Exception.Response) { return '' } + $stream = $Exception.Response.GetResponseStream() + if ($null -eq $stream) { return '' } + $reader = New-Object System.IO.StreamReader($stream) + $body = $reader.ReadToEnd() + $reader.Close() + return $body + } catch { + return '' + } +} + function New-SacEventPayload { param( [Parameter(Mandatory = $true)][string]$EventType, @@ -101,6 +128,9 @@ function New-SacEventPayload { [hashtable]$Details = $null ) + $Title = Limit-SacString -Text $Title -MaxLen 256 -Label 'title' + $Summary = Limit-SacString -Text $Summary -MaxLen 8192 -Label 'summary' + $payload = [ordered]@{ schema_version = '1.0' event_id = [guid]::NewGuid().ToString() @@ -209,6 +239,19 @@ function Remove-SacSpoolFile { } } +function Move-SacSpoolToRejected { + param([string]$EventId) + $dir = Get-SacSpoolDirResolved + $src = Join-Path $dir "$EventId.json" + if (-not (Test-Path -LiteralPath $src)) { return } + $rejDir = Join-Path $dir 'rejected' + if (-not (Test-Path -LiteralPath $rejDir)) { + New-Item -ItemType Directory -Path $rejDir -Force | Out-Null + } + $dst = Join-Path $rejDir "$EventId.json" + Move-Item -LiteralPath $src -Destination $dst -Force -ErrorAction SilentlyContinue +} + function Invoke-SacPostPayload { param([string]$JsonBody) @@ -222,6 +265,7 @@ function Invoke-SacPostPayload { $eventId = [string]$obj.event_id $eventType = [string]$obj.type $timeout = if ($SacTimeoutSec) { [int]$SacTimeoutSec } else { 12 } + $spoolOnFailure = $true try { Invoke-SacTlsPrep @@ -239,14 +283,38 @@ function Invoke-SacPostPayload { return $true } $body = if ($resp.Content) { $resp.Content } else { '' } - Write-SacLog "WARN: SAC POST HTTP $($resp.StatusCode): $($body.Substring(0, [Math]::Min(200, $body.Length)))" + if ($resp.StatusCode -eq 422) { + $spoolOnFailure = $false + Write-SacLog "WARN: SAC POST HTTP 422 validation (not spooled) type=$eventType event_id=$eventId" + } + if ($body.Length -gt 0) { + $snippet = $body.Substring(0, [Math]::Min(800, $body.Length)) + Write-SacLog "WARN: SAC POST HTTP $($resp.StatusCode): $snippet" + } else { + Write-SacLog "WARN: SAC POST HTTP $($resp.StatusCode) (empty body)" + } } catch { $code = 0 + $body = Get-SacHttpErrorBody -Exception $_.Exception if ($_.Exception.Response) { $code = [int]$_.Exception.Response.StatusCode } + if ($code -eq 422) { + $spoolOnFailure = $false + Write-SacLog "WARN: SAC POST HTTP 422 validation (not spooled) type=$eventType event_id=$eventId" + } $err = $_.Exception.Message - Write-SacLog "WARN: SAC POST HTTP ${code}: $err" + if ($body.Length -gt 0) { + $snippet = $body.Substring(0, [Math]::Min(800, $body.Length)) + Write-SacLog "WARN: SAC POST HTTP ${code}: $err | $snippet" + } else { + Write-SacLog "WARN: SAC POST HTTP ${code}: $err" + } + } + + if (-not $spoolOnFailure) { + Move-SacSpoolToRejected -EventId $eventId + return $false } Write-SacSpoolFile -EventId $eventId -JsonBody $JsonBody @@ -371,9 +439,12 @@ function Invoke-SacFlushSpool { $count++ if ($count -gt $MaxFiles) { break } try { - $json = [System.IO.File]::ReadAllText($f.FullName) + $utf8 = New-Object System.Text.UTF8Encoding $false + $json = [System.IO.File]::ReadAllText($f.FullName, $utf8) Invoke-SacPostPayload -JsonBody $json | Out-Null - } catch { } + } catch { + Write-SacLog "WARN: SAC spool flush failed for $($f.Name): $($_.Exception.Message)" + } } } diff --git a/version.txt b/version.txt index 65d1832..cc1c037 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.8-SAC +1.2.9-SAC