nsmcp::CachedLogSearch method search (public)

 <instance of nsmcp::CachedLogSearch[i]> 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
[ hide source ] | [ make this the default ]
Show another procedure: