nsmcp::CachedLogSearch method errorSummary (public)
<instance of nsmcp::CachedLogSearch> errorSummary options \ redactor
Defined in /usr/local/ns/tcl/nsmcp/log-summary.tcl
- Parameters:
- options (required)
- redactor (required)
- Testcases:
- No testcase defined.
Source code: set from [dict get $options from] set to [dict get $options to] set maxGroups [dict get $options maxGroups] file stat ${:path} before set source [open ${:path} rb] set groups {} set matched 0 set ungrouped 0 set unknownTime 0 set unrecognized 0 set examples {} set severityTotals {Error 0 Fatal 0 Bug 0} set pending false set clipped 0 set firstTime {} set lastTime {} set deadline [expr {[clock milliseconds]+1000}] set start 0 set examined 0 set stable true set prefix {} if {[dict exists $options scanState]} { set state [dict get $options scanState] foreach name {groups matched ungrouped unknownTime unrecognized examples severityTotals pending clipped firstTime lastTime start examined prefix} { set $name [dict get $state $name] } if {[list $before(dev) $before(ino)] ne [dict get $state identity] || $before(size) < [dict get $state sourceBytes]} { close $source error "system log changed during summary scan; start a new summary" } set before(size) [dict get $state sourceBytes] } set chunkEnd [expr {min($before(size),$examined+4194304)}] set reader {} try { set currentPrefix [read $source [expr {min(256,$before(size))}]] if {$prefix ne "" && $prefix ne $currentPrefix} { error "system log changed during summary scan; start a new summary" } set prefix $currentPrefix seek $source $examined set reader [::nsmcp::LogSummaryReader new -channel $source -limit $before(size) -offset $examined -maxBytes ${:maxMessageBytes}] while {$examined < $chunkEnd && [clock milliseconds] < $deadline} { set offset $examined set event [$reader next] set examined [dict get $event endOffset] if {$examined <= $offset} {break} if {![dict get $event complete]} {set pending true} # Most records are Notice/Debug output. Avoid parser dispatch and # metadata allocation for valid headers outside the counted levels. if {[regexp {^\[[^\]\r\n]+\]\[[^\]\r\n]+\]\[[^\]\r\n]+\][ \t]+([A-Za-z][A-Za-z0-9_()-]*):[ \t]*} [dict get $event message] -> level] && $level ni {Error Fatal Bug}} {continue} set meta [${:recordParser} metadata [dict get $event message]] set severity [dict get $meta severity] if {$severity eq ""} { incr unrecognized if {[llength $examples] < 5} { lappend examples [list offset number $offset endOffset number $examined example string [string range [$redactor message [dict get $event message]] 0 511] truncated boolean [expr {[dict get $event truncated] || [string length [dict get $event message]]>512}]] } continue } if {$severity ni {Error Fatal Bug}} {continue} set timestamp [dict get $meta timestamp] set seconds {} # NaviServer system timestamps use the process's local timezone. # Optional explicit offsets override it; fractional seconds survive. if {[regexp {^([0-9]{2}/[A-Za-z]{3}/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2})(\.[0-9]+)?( [+-][0-9]{4})?$} $timestamp -> base fraction zone]} { set format {%d/%b/%Y:%H:%M:%S} if {$zone ne ""} {append base $zone; append format { %z}} if {![catch {clock scan $base -format $format -locale C} seconds] && $fraction ne ""} { set seconds [expr {$seconds+double("0$fraction")}] } } if {$seconds eq "" || ![string is double -strict $seconds]} {incr unknownTime; continue} if {$seconds < $from || $seconds >= $to} {continue} incr matched dict incr severityTotals $severity if {$firstTime eq "" || $seconds < $firstTime} {set firstTime $seconds} if {$lastTime eq "" || $seconds > $lastTime} {set lastTime $seconds} set text [$redactor message [dict get $event message]] # Group by redacted first-line text, normalizing numeric tokens and # addresses in hex. Full examples retain their original values. set heading [lindex [split $text \n] 0] regsub {^\[[^\]]+\]\[[^\]]+\]\[[^\]]+\][ \t]+[A-Za-z]+:[ \t]*} $heading {} heading set pattern [string range $heading 0 511] regsub -all {0x[0-9A-Fa-f]+} $pattern {<hex>} pattern regsub -all {\m[0-9]+\M} $pattern {<n>} pattern set key [list $severity $pattern] if {![dict exists $groups $key]} { if {[dict size $groups] >= $maxGroups} {incr ungrouped; continue} dict set groups $key [dict create severity $severity pattern $pattern count 0 firstAt $seconds lastAt $seconds example [string range $text 0 2047] requestIdentifier [dict get $meta requestIdentifier] exampleTruncated false] } set group [dict get $groups $key] dict incr group count dict set group firstAt [expr {min([dict get $group firstAt],$seconds)}] dict set group lastAt [expr {max([dict get $group lastAt],$seconds)}] if {[dict get $event truncated] || [string length $text]>2048} { dict set group exampleTruncated true incr clipped } dict set groups $key $group } file stat ${:path} after seek $source 0 set stable [expr {[list $before(dev) $before(ino)] eq [list $after(dev) $after(ino)] && $after(size)>=$before(size) && [read $source [string length $prefix]] eq $prefix}] } finally { if {$reader ne ""} {$reader destroy} close $source } if {!$stable} {error "system log changed during summary scan; start a new summary"} set state {} if {$examined < $before(size)} { foreach name {groups matched ungrouped unknownTime unrecognized examples severityTotals pending clipped firstTime lastTime start examined prefix} { dict set state $name [set $name] } dict set state identity [list $before(dev) $before(ino)] dict set state sourceBytes $before(size) } set exampleEntries {} set i 0 foreach example $examples {lappend exampleEntries $i object $example; incr i} set severityFields {} dict for {severity count} $severityTotals {lappend severityFields $severity number $count} set ordered {} dict for {key group} $groups {lappend ordered [list [dict get $group count] $group]} set entries {} set index 0 foreach pair [lsort -integer -decreasing -index 0 $ordered] { set group [lindex $pair 1] set fields {} foreach name {severity pattern example requestIdentifier} {lappend fields $name string [dict get $group $name]} foreach name {count firstAt lastAt} {lappend fields $name number [dict get $group $name]} lappend fields exampleTruncated boolean [dict get $group exampleTruncated] lappend entries $index object $fields incr index } set result [list {} object [list server string [ns_info server] scope string process from number $from to number $to timezone string [dict get $options timezone] timestampTimezone string [clock format [clock seconds] -format %Z] severityTotals object $severityFields matchedRecords number $matched ungroupedRecords number $ungrouped groups array $entries grouping string {Redacted first line; numeric tokens and hex addresses normalized. Groups are bounded by first encounter, then sorted by count.} coverage object [list sourceBytes number $before(size) examinedFromOffset number $start examinedToOffset number $examined sourceFullyExamined boolean [expr {$stable && $start==0 && $examined==$before(size)}] sourceChanged boolean [expr {!$stable}] historyUnreadBytes number $start unreadBytes number [expr {max(0,$before(size)-$examined)}] unknownTimeErrors number $unknownTime unrecognizedRecords number $unrecognized unrecognizedExamples array $exampleEntries clippedExamples number $clipped pendingFinalRecord boolean $pending rotationsExamined boolean false windowComplete boolean false limitations string {Only the configured current system log is examined. Rotated files and time before this file are not certified. Counts include observed final records, whose continuations may still grow.}]]] if {$state ne ""} {lappend result $state} return $resultXQL Not present: Generic, PostgreSQL, Oracle
![[i]](/resources/acs-subsite/ZoomIn16.gif)