nsmcp::CachedLogSearch method search (public)
<instance of nsmcp::CachedLogSearch> search literal [ severity ] \ [ correlationField ]
Defined in /usr/local/ns/tcl/nsmcp/lib/log-search.tcl
- Parameters:
- literal (required)
- severity (optional)
- correlationField (optional)
- Testcases:
- No testcase defined.
Source code: if {$correlationField ni {{} system access}} { error "expected an empty, system or access correlation field" } set identifierCheck [expr {$correlationField eq "access" ? "validConnectionIdentifier" : "validRequestIdentifier"}] if {$correlationField ne "" && ![${:recordParser} $identifierCheck $literal]} { error "correlation requires a complete request identifier" } if {$severity ne "" && ![regexp {^[A-Za-z][A-Za-z0-9_()-]*$} $severity]} { error "expected a log severity name" } if {$literal eq "" || [string length [encoding convertto utf-8 $literal]] > 4096 || [string first \n $literal] >= 0 || [string first \r $literal] >= 0 || [string first \x00 $literal] >= 0} { error "expected a nonempty, single-line literal search key of at most 4096 bytes" } ns_mutex lock ${:mutex} try { set metaKey ${:cacheKey}:search set queryKey [ns_crypto::md string -digest sha256 -- [list access-metadata-v5 $literal $severity $correlationField]] file stat ${:path} before set source [open ${:path} rb] set scanner [open ${:path} rb] try { fconfigure $source -translation binary -encoding binary fconfigure $scanner -translation binary -encoding binary file stat ${:path} opened set identity [list $before(dev) $before(ino)] if {$identity ne [list $opened(dev) $opened(ino)]} {error "log rotated while opening; retry"} set reset false; set generation 1 if {[nsv_exists ${:store} $metaKey]} { set meta [nsv_get ${:store} $metaKey] set generation [dict get $meta generation] set prefix [dict get $meta prefix] if {$identity ne [dict get $meta identity] || $before(size) < [dict get $meta observedSize] || [read $source [string length $prefix]] ne $prefix} { :removeQueries [dict get $meta queries] set reset true; incr generation } } else {set reset true} if {$reset} { seek $source 0 set meta [dict create identity $identity generation $generation observedSize $before(size) queries {} prefix [read $source [expr {min(256,$before(size))}]]] } if {[dict get $meta prefix] eq "" && $before(size) > 0} { seek $source 0 dict set meta prefix [read $source [expr {min(256,$before(size))}]] } set queries [dict get $meta queries] if {![dict exists $queries $queryKey]} { if {[dict size $queries] >= ${:maxSearches}} { set oldest {}; set oldestTime {} dict for {key query} $queries { if {$oldest eq "" || [dict get $query used] < $oldestTime} { set oldest $key; set oldestTime [dict get $query used] } } :removeQueries [dict create $oldest [dict get $queries $oldest]] dict unset queries $oldest } dict set queries $queryKey [dict create resultFile [:temporaryFile] pendingFile [:temporaryFile] resultHandle [ns_crypto::randombytes -encoding hex 32] cursor 0 resultCount 0 fileBytes 0 pending false used [clock clicks -milliseconds] cacheFull false sourceBytes -1 scanIncomplete true] } set query [dict get $queries $queryKey] # Publish ownership before processing, so an interrupted scan's # files remain discoverable for invalidation/eviction. dict set meta queries $queries nsv_set ${:store} $metaKey $meta if {[dict get $query sourceBytes] == $before(size) && ![dict get $query scanIncomplete]} { dict set query used [clock clicks -milliseconds] dict set meta queries $queryKey $query dict set meta observedSize $before(size) nsv_set ${:store} $metaKey $meta return [dict merge $query [dict create generation $generation reset $reset]] } set output [open [dict get $query resultFile] r+] set pending [open [dict get $query pendingFile] w] try { fconfigure $output -encoding utf-8 -translation lf fconfigure $pending -encoding utf-8 -translation lf # Discard writes beyond the last published checkpoint after # a failed scan, rather than appending duplicate results. ns_ftruncate $output [dict get $query fileBytes] seek $output 0 end dict set query pending false set cursor [dict get $query cursor]; set matched 0 while {$cursor < $before(size) && $matched < ${:maxMatchesPerCall} && ![dict get $query cacheFull]} { # Keep native searching separate from buffered Tcl record reads. seek $scanner $cursor set found [ns_fseekchars $scanner $literal] if {$found < 0 || $found >= $before(size)} { set cursor [:recordStart $source [expr {$before(size)-1}] $before(size)] break } set start [:recordStart $source $found $before(size)] set event [:record $source $start $before(size)] incr matched set metadata [${:recordParser} metadata [dict get $event message]] set accepted [expr {$severity eq "" || [dict get $metadata severity] eq $severity}] if {$correlationField eq "system"} { set accepted [expr {$accepted && [dict get $metadata requestIdentifier] eq $literal}] } elseif {$correlationField eq "access"} { set accepted [expr {$accepted && [${:recordParser} accessRequestIdentifier [dict get $event message]] eq $literal}] } if {!$accepted} { if {![dict get $event complete]} {set cursor $start; break} set cursor [dict get $event endOffset] continue } set json [:encodeEvent $event] if {![dict get $event complete]} { puts $pending $json dict set query pending true set cursor $start break } set bytes [string length [encoding convertto utf-8 $json\n]] if {[tell $output]+$bytes > ${:maxCacheBytes}} { dict set query cacheFull true set cursor $start break } puts $output $json dict incr query resultCount set cursor [dict get $event endOffset] } flush $output dict set query fileBytes [tell $output] dict set query cursor $cursor dict set query used [clock clicks -milliseconds] } finally {close $output; close $pending} file stat ${:path} after seek $source 0 set prefix [dict get $meta prefix] if {$identity ne [list $after(dev) $after(ino)] || $after(size) < $before(size) || [read $source [string length $prefix]] ne $prefix} { error "log changed during scan; retry" } # cursor may point to the pending final message. Distinguish that # deliberate recheck from a match-budget backlog. dict set query scanIncomplete [expr {$matched >= ${:maxMatchesPerCall} || [dict get $query cacheFull]}] dict set query sourceBytes $before(size) dict set meta observedSize $before(size) dict set meta queries $queryKey $query nsv_set ${:store} $metaKey $meta return [dict merge $query [dict create generation $generation reset $reset]] } finally {close $source; close $scanner} } finally {ns_mutex unlock ${:mutex}}XQL Not present: Generic, PostgreSQL, Oracle
![[i]](/resources/acs-subsite/ZoomIn16.gif)