nsmcp::CachedLogSearch method errorSummary (public)

 <instance of nsmcp::CachedLogSearch[i]> 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 $result
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: