Public/Get-SnowSyslog.ps1
|
function Get-SnowSyslogNewRow { # Returns only rows not yet seen, given the newest timestamp and the sys_ids already emitted at that timestamp. param( [AllowEmptyCollection()][object[]]$Row = @(), [string]$LastCreated, [System.Collections.Generic.HashSet[string]]$SeenIds ) foreach ($item in $Row) { $created = [string]$item.sys_created_on if ($LastCreated -and $created -lt $LastCreated) { continue } if ($LastCreated -and $created -eq $LastCreated -and $SeenIds.Contains([string]$item.sys_id)) { continue } $item } } function Write-SnowSyslogLine { param([Parameter(Mandatory)][object]$Row) $level = if ($Row.PSObject.Properties['level']) { [string]$Row.level } else { '' } $name = switch ($level) { { $_ -in '2', 'error' } { 'error' } { $_ -in '1', 'warning' } { 'warning' } { $_ -in '3', 'debug' } { 'debug' } default { 'info' } } $color = switch ($name) { 'error' { 'Red' } 'warning' { 'Yellow' } 'debug' { 'DarkGray' } default { 'Gray' } } $source = if ($Row.PSObject.Properties['source']) { [string]$Row.source } else { '' } $message = if ($Row.PSObject.Properties['message']) { [string]$Row.message } elseif ($Row.PSObject.Properties['url']) { [string]$Row.url } else { '' } Write-Host ('{0} {1,-7} {2} {3}' -f $Row.sys_created_on, $name.ToUpperInvariant(), $source, $message) -ForegroundColor $color } function Get-SnowSyslog { <# .SYNOPSIS Queries the ServiceNow system log, optionally following new entries like tail -f. .DESCRIPTION Rows are returned oldest first. A time or row limit is always applied: by default the last 15 minutes, capped at 100 rows. With -Wait the log is polled every -PollSeconds (minimum 2) and only new rows are emitted; the last seen sys_created_on plus the sys_ids seen at that timestamp prevent duplicates and gaps. Failed polls back off and retry. Press Ctrl+C to stop. Use -Pretty for coloured console output instead of objects. .PARAMETER Table syslog (default), syslog_transaction or syslog_app_scope. .PARAMETER Level error, warning, info or debug. .PARAMETER Source Filters on the log source. .PARAMETER Message Filters on text contained in the message. .PARAMETER User Filters on the user (sys_created_by). .PARAMETER Since Returns rows created after this time. .PARAMETER Last The maximum number of rows to return (default 100). .PARAMETER Wait Follows new rows until stopped. .PARAMETER PollSeconds Seconds between polls when -Wait is used (2-3600, default 5). .PARAMETER MaxPolls Stops after this many polls; 0 means unlimited. .PARAMETER Pretty Writes coloured lines to the console instead of returning objects. .PARAMETER Fields The fields to return. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE Get-SnowSyslog -Level error -Last 50 Shows the last 50 errors from the past 15 minutes. .EXAMPLE Get-SnowSyslog -Wait -Pretty -Source 'Flow Engine' -Message timeout -PollSeconds 10 Watches for flow timeouts while you reproduce a problem. .EXAMPLE Get-SnowSyslog -Level error -Since (Get-Date).AddHours(-2) -Last 500 | Group-Object source | Sort-Object Count -Descending | Select-Object -First 5 Name, Count Finds the noisiest error sources in the last two hours. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [ValidateSet('syslog', 'syslog_transaction', 'syslog_app_scope')][string]$Table = 'syslog', [ValidateSet('error', 'warning', 'info', 'debug')][string]$Level, [string]$Source, [string]$Message, [string]$User, [datetime]$Since, [ValidateRange(1, 10000)][int]$Last = 100, [switch]$Wait, [ValidateRange(2, 3600)][int]$PollSeconds = 5, [ValidateRange(0, 1000000)][int]$MaxPolls = 0, [switch]$Pretty, [string[]]$Fields, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) if ($Table -eq 'syslog_transaction' -and ($Level -or $Source)) { throw [System.ArgumentException]::new('-Level and -Source are not supported for syslog_transaction.') } $levelValues = @{ info = '0'; warning = '1'; error = '2'; debug = '3' } $terms = [System.Collections.Generic.List[string]]::new() if ($Level) { $terms.Add("level=$($levelValues[$Level])") } if ($Source) { $terms.Add("source=$(ConvertTo-SnowQueryValue $Source)") } if ($Message) { $messageField = if ($Table -eq 'syslog_transaction') { 'url' } else { 'message' } $terms.Add("${messageField}LIKE$(ConvertTo-SnowQueryValue $Message)") } if ($User) { $terms.Add("sys_created_by=$(ConvertTo-SnowQueryValue $User)") } $sinceTime = if ($PSBoundParameters.ContainsKey('Since')) { $Since.ToUniversalTime() } else { [datetime]::UtcNow.AddMinutes(-15) } $format = 'yyyy-MM-dd HH:mm:ss' $sinceText = $sinceTime.ToString($format, [Globalization.CultureInfo]::InvariantCulture) $terms.Add("sys_created_on>=$sinceText") $filter = $terms -join '^' $request = @{ Table = $Table; Fields = $Fields; Backend = $Backend } if ($Fields) { $requiredFields = @('sys_id', 'sys_created_on') if ($Pretty) { if ($Table -eq 'syslog_transaction') { $requiredFields += 'url' } else { $requiredFields += @('level', 'source', 'message') } } $request.Fields = @($Fields + $requiredFields | Select-Object -Unique) } $emit = { param($row) if ($Pretty) { Write-SnowSyslogLine -Row $row } else { if ($row -is [System.Collections.IDictionary]) { $row = [pscustomobject]$row } if ($row -is [pscustomobject]) { $row.PSTypeNames.Insert(0, "Snowplow.Record.$Table") } $row } } # Initial query: newest -Last rows within the window, then oldest first. $initial = @(Invoke-SnowBackendRequest @request -Query "$filter^ORDERBYDESCsys_created_on^ORDERBYDESCsys_id" -PageSize ([Math]::Min($Last, 1000)) -First $Last) [array]::Reverse($initial) $lastCreated = $sinceText $seen = [System.Collections.Generic.HashSet[string]]::new() foreach ($row in $initial) { $created = [string]$row.sys_created_on if ($created -gt $lastCreated) { $lastCreated = $created; $seen.Clear() } if ($created -eq $lastCreated) { [void]$seen.Add([string]$row.sys_id) } & $emit $row } if (-not $Wait) { return } Write-Warning "Following $Table every $PollSeconds second(s). Each poll is a request against the instance; press Ctrl+C to stop." $failures = 0 $polls = 0 try { while ($true) { $delay = if ($failures) { [Math]::Min($PollSeconds * [Math]::Pow(2, $failures), 300) } else { $PollSeconds } Start-SnowSleep -Seconds $delay $polls++ $pollFilter = (@($terms | Where-Object { $_ -notlike 'sys_created_on>=*' }) + "sys_created_on>=$lastCreated") -join '^' try { $rows = @(Invoke-SnowBackendRequest @request -Query "$pollFilter^ORDERBYsys_created_on^ORDERBYsys_id" -PageSize 1000) $failures = 0 } catch { $failures++ if ($failures -ge 5) { throw } Write-Warning "Syslog poll failed ($($_.Exception.Message)); backing off." $rows = @() } foreach ($row in @(Get-SnowSyslogNewRow -Row $rows -LastCreated $lastCreated -SeenIds $seen)) { $created = [string]$row.sys_created_on if ($created -gt $lastCreated) { $lastCreated = $created; $seen.Clear() } [void]$seen.Add([string]$row.sys_id) & $emit $row } if ($MaxPolls -and $polls -ge $MaxPolls) { break } } } finally { Write-Verbose 'Syslog follow stopped.' } } |