Class ::nsmcp::AccessRecordParser (public)
::nx::Class ::nsmcp::AccessRecordParser
Defined in /usr/local/ns/tcl/nsmcp/access-record.tcl
-1 detects common/combined layouts; -2 disables timing detection. Explicit 0 (common) or 2 (combined) can pin the configured column.
- Testcases:
- No testcase defined.
Source code: :property {timingColumn:integer -1} :method init {} { if {${:timingColumn} ni {-2 -1 0 2}} {error "timingColumn must be -2, -1, 0, or 2"} } :public method completionTime {line} { if {![regexp {^\S+\s+\S+\s+(?:"(?:[^"\\]|\\.)*"|\S+)\s+(\[[^\]\r\n]+\]|[0-9]+)\s+"} $line -> stamp]} {return {}} set stamp [string trim $stamp {[]}] if {[string is entier -strict $stamp]} {return $stamp} if {[catch {clock scan $stamp -format {%d/%b/%Y:%H:%M:%S %z} -locale C} time]} {return {}} return $time } # Called only for rejected rows, so diagnostics add no valid-row overhead. :public method failureReason {line} { if {[string first \x00 $line]>=0} {return layout} if {![regexp {^\S+\s+\S+\s+(?:"(?:[^"\\]|\\.)*"|\S+)\s+(\[[^\]\r\n]+\]|[0-9]+)\s+"((?:[^"\\\r\n]|\\.)*)"\s+([1-5][0-9]{2})\s+(-|[0-9]+)(.*)$} $line]} {return layout} if {[:completionTime $line] eq ""} {return timestamp} return request } :method timingFields {timestamp fields} { set result {} set index ${:timingColumn} if {$index == -1} {set index [expr {[llength $fields]>=2 ? 2 : 0}]} foreach index [expr {$index<0 ? {} : [list $index]}] { set field [string trim [lindex $fields $index] {"}] if {![regexp {^([0-9]+\.[0-9]+) ([0-9]+(?:\.[0-9]+)?) ([0-9]+(?:\.[0-9]+)?) ([0-9]+(?:\.[0-9]+)?) ([0-9]+(?:\.[0-9]+)?)$} $field -> start accept queue filter run]} continue if {$start < 946684800} continue if {$start > [clock seconds]+86400} { # The normal parser falls back today, but this start value can # become valid later. Such a fallback is not reusable evidence # for excluding a time range on another day. dict set result timeDependent true continue } dict set result startTime $start dict set result timingSource start foreach {key value} [list acceptTime $accept queueTime $queue filterTime $filter runTime $run] { dict set result $key [expr {double($value)}] } break } if {![dict exists $result startTime]} { if {[string is entier -strict $timestamp]} { set time $timestamp } elseif {[catch {clock scan $timestamp -format {%d/%b/%Y:%H:%M:%S %z} -locale C} time]} {return {}} dict set result startTime $time dict set result timingSource completion } return $result } :public method indexTime {line} { if {[string first \x00 $line]>=0} {return {}} if {![regexp {^\S+\s+\S+\s+(?:"(?:[^"\\]|\\.)*"|\S+)\s+(\[[^\]\r\n]+\]|[0-9]+)\s+"(?:[^"\\\r\n]|\\.)*"\s+[1-5][0-9]{2}\s+(?:-|[0-9]+)(.*)$} $line -> stamp tail]} {return {}} set fields [regexp -all -inline {"(?:[^"\\]|\\.)*"} $tail] set timing [:timingFields [string trim $stamp {[]}] $fields] if {$timing eq "" || [dict exists $timing timeDependent]} {return {}} return [dict get $timing startTime] } :public method parse {line} { if {[string first \x00 $line]>=0} {return {}} if {![regexp {^(\S+)\s+(\S+)\s+("(?:[^"\\]|\\.)*"|\S+)\s+(\[[^\]\r\n]+\]|[0-9]+)\s+"((?:[^"\\\r\n]|\\.)*)"\s+([1-5][0-9]{2})\s+(-|[0-9]+)(.*)$} $line -> peer identifier user stamp request status bytes tail]} {return {}} set user [string trim $user {"}] set authenticationState [expr {$user in {0 -} ? "anonymous" : ($user in {{} {[REDACTED]}} ? "unknown" : "authenticated")}] set timestamp [string trim $stamp {[]}] set result [dict create authenticationState $authenticationState timestamp $timestamp peerAddress $peer requestIdentifier {} pool unknown method unknown url {} status [scan $status %d] responseBytes 0 timingSource completion] if {$bytes ne "-"} {dict set result responseBytes [scan $bytes %lld]} if {[::nsmcp::logRecordParser validRequestIdentifier $identifier]} { dict set result requestIdentifier $identifier } # Only connection headers identify a connection pool. if {[::nsmcp::logRecordParser validConnectionIdentifier $identifier]} { dict set result pool [lindex [split $identifier :] 2] } # Empty/binary/probe requests remain valid access records. Extract the # ends of a request line, allowing embedded spaces in the logged target. set first [string first " " $request] set last [string last " " $request] if {$first>0 && $last>$first} { set protocol [string range $request [expr {$last+1}] end] if {[string match */* $protocol]} { dict set result method [string range $request 0 [expr {$first-1}]] dict set result url [string range $request [expr {$first+1}] [expr {$last-1}]] dict set result protocol $protocol } } elseif {[string match /* $request]} { dict set result url $request } set fields [regexp -all -inline {"(?:[^"\\]|\\.)*"} $tail] # Combined access format has referer and User-Agent as its first two # quoted fields. Common format has no User-Agent; leave it unknown. if {[llength $fields]>=2 && ${:timingColumn} in {-2 -1 2}} { dict set result userAgent [string range [lindex $fields 1] 1 end-1] } set timing [:timingFields $timestamp $fields] if {$timing eq ""} {return {}} set result [dict merge $result $timing] set path [lindex [split [dict get $result url] ?] 0] if {[regexp {^[A-Za-z][A-Za-z0-9+.-]*://[^/]*(/.*)?$} $path -> suffix]} { set path [expr {$suffix eq "" ? "/" : $suffix}] } dict set result path $path return $result }XQL Not present: Generic, PostgreSQL, Oracle
![[i]](/resources/acs-subsite/ZoomIn16.gif)