Class ::nsmcp::LogService (public)

 ::nx::Class ::nsmcp::LogService[i]

Defined in /usr/local/ns/tcl/nsmcp/lib/log-tools.tcl

Testcases:
No testcase defined.
Source code:
    :property {analytics {}}
    :property correlation:required
    :property authorizer:required
    :property redactor:required
    :property peerRedactor:required
    :property store:required
    :property queue:required
    :property {lifetime:integer 900}
    :property {maxJobs:integer 128}
    :variable mutex
    :method init {} {
        if {${:lifetime} < 1 || ${:maxJobs} < 1} {error "positive log service limits required"}
        set :mutex [ns_mutex create]
        if {${:queue} ni [ns_job queues]} {ns_job create ${:queue} 1}
    }
    :method require {context} {${:authorizer} require $context diagnostics.logs.read}
    :method owned {context record} {
        return [expr {[dict get $record principal] eq [$context principal] &&
            [dict get $record server] eq [$context server] && [dict get $record expires] > [clock seconds]}]
    }
    :public method start {context operation {identifier {}}} {
        :require $context
        if {$operation ni {errors requestContext accessSummary}} {error "invalid log operation"}
        if {$operation eq "requestContext" && ![::nsmcp::logRecordParser validRequestIdentifier $identifier]} {
            return -code error -errorcode {NSMCP RPC -32602} "Invalid request identifier"
        }
        set requiresPeers [expr {$operation eq "accessSummary" &&
            ([dict exists $identifier peer] || ([dict exists $identifier includePeerAddresses] && [dict get $identifier includePeerAddresses]))}]
        if {$requiresPeers} {${:authorizer} require $context diagnostics.logs.peers.read}
        ns_mutex lock ${:mutex}
        try {
            foreach key [nsv_array names ${:store}] {
                set record [nsv_get ${:store} $key]
                if {[dict get $record expires] <= [clock seconds] && [dict get $record status] in {complete failed}} {
                    nsv_unset ${:store} $key
                }
            }
            if {[llength [nsv_array names ${:store}]] >= ${:maxJobs}} {error "log job limit reached"}
            set handle [ns_crypto::randombytes -encoding hex 32]
            nsv_set ${:store} $handle [dict create principal [$context principal] server [$context server]  expires [expr {[clock seconds]+${:lifetime}}] status queued mappings {} requiresPeers $requiresPeers]
            try {
                ns_job queue -detached ${:queue} [list [self] run $handle $operation $identifier]
            } on error {message options} {
                nsv_unset ${:store} $handle
                return -options $options $message
            }
        } finally {ns_mutex unlock ${:mutex}}
        return [list {} object [list jobHandle string $handle status string queued server string [ns_info server]]]
    }
    :method summary {snapshot mappingsVar} {
        upvar 1 $mappingsVar mappings
        set handle [ns_crypto::randombytes -encoding hex 32]
        dict set mappings $handle [dict get $snapshot resultHandle]
        set triples [list resultHandle string $handle server string [ns_info server]]
        foreach key {resultCount generation sourceBytes} {lappend triples $key number [dict get $snapshot $key]}
        foreach key {scanIncomplete pending cacheFull} {lappend triples $key boolean [dict get $snapshot $key]}
        return $triples
    }
    :public method run {handle operation identifier} {
        ns_mutex lock ${:mutex}
        try {
            if {![nsv_exists ${:store} $handle]} return
            set record [nsv_get ${:store} $handle]
            dict set record status running
            nsv_set ${:store} $handle $record
        } finally {ns_mutex unlock ${:mutex}}
        set mappings {}
        try {
            if {$operation eq "errors"} {
                set data [:summary [${:correlation} errors] mappings]
            } elseif {$operation eq "accessSummary"} {
                if {${:analytics} eq ""} {error "access analytics is not configured"}
                ${:analytics} ingest [dict get $identifier from]
                set data [::nsmcp::accessSummaryTriples [${:analytics} summary $identifier]]
            } else {
                set context [${:correlation} requestContext $identifier]
                set data [list server string [ns_info server] requestIdentifier string $identifier system object [:summary [dict get $context system] mappings]]
                set access {}; set index 0
                dict for {source snapshot} [dict get $context access] {
                    lappend access $index object [:summary $snapshot mappings]
                    incr index
                }
                lappend data access array $access
            }
            dict set record status complete
            dict set record data $data
            dict set record mappings $mappings
        } on error {message options} {
            dict set record status failed
            # No source paths, log contents, or exception details in client errors.
            ns_log error "nsmcp: background log operation failed: $message"
        }
        ns_mutex lock ${:mutex}
        try {nsv_set ${:store} $handle $record} finally {ns_mutex unlock ${:mutex}}
    }
    :public method poll {context handle} {
        :require $context
        ns_mutex lock ${:mutex}
        try {
            if {![nsv_exists ${:store} $handle] || ![:owned $context [nsv_get ${:store} $handle]]} {
                return -code error -errorcode {NSMCP RPC -32602} "Unknown or expired job handle"
            }
            set record [nsv_get ${:store} $handle]
            if {[dict exists $record requiresPeers] && [dict get $record requiresPeers]} {${:authorizer} require $context diagnostics.logs.peers.read}
            set data [list jobHandle string $handle status string [dict get $record status] server string [ns_info server]]
            if {[dict get $record status] eq "complete"} {lappend data result object [dict get $record data]}
            if {[dict get $record status] eq "failed"} {lappend data error string {Log search failed; consult the server log.}}
            return [list {} object $data]
        } finally {ns_mutex unlock ${:mutex}}
    }
    :public method results {context handle cursor limit includePeers} {
        :require $context
        if {$includePeers} {${:authorizer} require $context diagnostics.logs.peers.read}
        set internal {}
        ns_mutex lock ${:mutex}
        try {
            foreach key [nsv_array names ${:store}] {
                set record [nsv_get ${:store} $key]
                if {[:owned $context $record] && [dict exists $record mappings $handle]} {
                    set internal [dict get $record mappings $handle]; break
                }
            }
        } finally {ns_mutex unlock ${:mutex}}
        if {$internal eq ""} {
            return -code error -errorcode {NSMCP RPC -32602} "Unknown or expired result handle"
        }
        set page [${:correlation} results $internal $cursor $limit]
        set policy [expr {$includePeers ? ${:peerRedactor} : ${:redactor}}]
        set page [$policy page $page]
        set records {}; set index 0
        foreach entry [dict get $page records] {
            set fields {}
            foreach key {timestamp processThread logThread severity requestIdentifier peerAddress method url protocol message pool timingSource} {
                if {[dict exists $entry $key]} {lappend fields $key string [dict get $entry $key]}
            }
            foreach key {offset endOffset status responseBytes} {
                set value [dict get $entry $key]
                lappend fields $key [expr {$value eq "" ? "string" : "number"}] $value
            }
            foreach key {startTime acceptTime queueTime filterTime runTime} {
                if {[dict exists $entry $key]} {lappend fields $key number [dict get $entry $key]}
            }
            foreach key {complete truncated orphanContinuation redacted} {lappend fields $key boolean [dict get $entry $key]}
            lappend records $index object $fields
            incr index
        }
        set data [list server string [ns_info server] resultHandle string $handle records array $records nextCursor string [dict get $page nextCursor]]
        foreach key {resultCount generation} {lappend data $key number [dict get $page $key]}
        foreach key {more scanIncomplete pending cacheFull} {lappend data $key boolean [dict get $page $key]}
        return [list {} object $data]
    }
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: