Class ::nsmcp::SmtpLogTool
::nsmcp::SmtpLogTool
create ... \
[ -authorizer:required authorizer:required ] \
[ -description (default "Read recent nssmtpd sendlog or eventlog records from the current server's configured SMTP module. Defaults to eventlog, the last 60 minutes, and at most 20 newest matching records. Reads at most 1 MiB from the current file; does not scan rotated files or certify full-window coverage when earlier bytes remain unread. Reports malformed, oversized and unfinished lines separately. Filters use exact event/session/status matches; contains is a literal substring in the redacted record. Returns SMTP envelope addresses and diagnostic metadata, never message bodies. Peer addresses are masked unless includePeerAddresses is true and diagnostics.logs.peers.read is granted. Event records belonging to another virtual server are excluded; sendlog has no per-record server field, so its scope is the configured source. Does not send mail or change SMTP settings.") ] \
[ -inputSchema (default "{} object {type string object properties object {
log object {type string string enum array {0 string event 1 string send} default string event}
module object {type string string default string nssmtpd description string {Configured nssmtpd module name in the current virtual server.}}
lastMinutes object {type string integer minimum number 1 maximum number 1440 default number 60}
limit object {type string integer minimum number 1 maximum number 100 default number 20}
session object {type string string description string {Exact eventlog session identifier; not available in sendlog.}}
event object {type string string description string {Exact eventlog event name.}}
status object {type string string description string {Exact SMTP status code, for example 451 or 550.}}
contains object {type string string description string {Literal substring in the redacted record.}}
includePeerAddresses object {type string boolean default boolean false}
} additionalProperties boolean false}") ] \
[ -maxLineBytes:integer (default "16384") ] \
[ -maxReadBytes:integer (default "1048576") ] \
[ -name (default "smtp_logs") ] \
[ -outputSchema (default "") ] \
[ -peerRedactor:required peerRedactor:required ] \
[ -permission (default "diagnostics.logs.read") ] \
[ -redactor:required redactor:required ]
Defined in /usr/local/ns/tcl/nsmcp/lib/smtp-tools.tclClass Relations
- class: ::nx::Class
![[i]](/resources/acs-subsite/ZoomIn16.gif)
- superclass: ::nsmcp::Tool
![[i]](/resources/acs-subsite/ZoomIn16.gif)
::nx::Class create ::nsmcp::SmtpLogTool \
-superclass ::nsmcp::Tool
Methods (to be applied on instances)
execute (scripted, public)
<instance of nsmcp::SmtpLogTool
> execute context arguments
- Parameters:
- context (required)
- arguments (required)
- Testcases:
-
No testcase defined.
set options [:options $arguments]
set peers [dict get $options includePeerAddresses]
if {$peers} {${:authorizer} require $context diagnostics.logs.peers.read}
set server [$context server]
set kind [dict get $options log]
set module [dict get $options module]
set source [:path $server $module $kind]
set path [dict get $source path]
set fields [list server string $server module string $module log string $kind loggingEnabled boolean [dict get $source enabled] sourcePath string $path]
if {$path eq "" || ![file isfile $path] || ![file readable $path]} {
lappend fields available boolean false reason string {Configured SMTP log is missing or unreadable.}
return [list {} object $fields]
}
set now [clock seconds]
set from [expr {$now-60*[dict get $options lastMinutes]}]
file stat $path before
set end $before(size)
set start [expr {max(0,$end-${:maxReadBytes})}]
set channel [open $path rb]
try {
seek $channel $start
set bytes [read $channel [expr {$end-$start}]]
} finally {close $channel}
set readBytes [string length $bytes]
set discarded 0
if {$start > 0} {
set first [string first \n $bytes]
if {$first < 0} {set discarded $readBytes; set bytes ""} else {
set discarded [expr {$first+1}]
set bytes [string range $bytes $discarded end]
}
}
set position [expr {$start+$discarded}]
set pending [expr {$bytes ne "" && [string index $bytes end] ne "\n"}]
set lines [split $bytes \n]
set lines [lrange $lines 0 end-1]
set malformed 0
set oversized 0
set otherServers 0
set matches 0
set records {}
foreach line $lines {
set offset $position
incr position [expr {[string length $line]+1}]
if {[string length $line] > ${:maxLineBytes}} {incr oversized; continue}
if {$line eq ""} continue
if {[catch {:parse $kind [encoding convertfrom utf-8 [string trimright $line \r]]} record]} {
incr malformed
continue
}
if {$kind eq "event" && [dict get $record recordedServer] ne $server} {incr otherServers; continue}
set at [dict get $record timestampSeconds]
if {$at < $from || $at > $now} continue
set match true
foreach key {session event status} {
if {[dict exists $options $key] && [dict get $options $key] ne [dict get $record $key]} {set match false}
}
if {!$match} continue
set record [:redact $record $peers]
if {[dict exists $options contains] && [string first [dict get $options contains] $record] < 0} continue
incr matches
dict set record offset $offset
dict set record endOffset $position
lappend records $record
set records [lrange $records end-[expr {[dict get $options limit]-1}] end]
}
set changed [catch {file stat $path after}]
if {!$changed} {
set changed [expr {[list $before(dev) $before(ino)] ne [list $after(dev) $after(ino)] || $after(size) < $end}]
}
if {$changed} {set records {}; set matches 0}
set array {}
set index 0
foreach record [lreverse $records] {
lappend array $index object [:recordTriples $record]
incr index
}
set complete [expr {$start == 0 && !$pending && !$changed && !$malformed && !$oversized}]
lappend fields available boolean true from number $from to number $now order string newest-first records array $array matchesInTail number $matches omittedMatches number [expr {$matches-$index}] coverage object [list sourceBytes number $end readBytes number $readBytes initialOffset number $start examinedFromOffset number [expr {$start+$discarded}] historyUnreadBytes number $start tailOnly boolean [expr {$start > 0}] pending boolean $pending malformed number $malformed oversized number $oversized otherServerRecords number $otherServers sourceChanged boolean $changed fileScanComplete boolean $complete rotatedFilesExamined boolean false]
return [list {} object $fields]validate (scripted, public)
<instance of nsmcp::SmtpLogTool
> validate arguments
- Parameters:
- arguments (required)
- Testcases:
-
No testcase defined.
if {[catch {:options $arguments} message]} {
return -code error -errorcode {NSMCP RPC -32602} $message
}