Class ::nsmcp::AccessRecordParser

::nsmcp::AccessRecordParser[i] create ... \
           [ -timingColumn:integer (default "-1") ]

-1 detects common/combined layouts; -2 disables timing detection. Explicit 0 (common) or 2 (combined) can pin the configured column.
Defined in /usr/local/ns/tcl/nsmcp/access-record.tcl

Class Relations

  • class: ::nx::Class[i]
  • superclass: ::nsmcp::Object[i]
::nx::Class create ::nsmcp::AccessRecordParser \
     -superclass ::nsmcp::Object

Methods (to be applied on instances)

  • completionTime (scripted, public)

     <instance of nsmcp::AccessRecordParser[i]> completionTime line

    Extract the completion timestamp from an access-log line. Accept the supported epoch or common-log timestamp without interpreting request data as executable Tcl.

    Parameters:
    line (required)
    Raw physical log line to inspect.
    Returns:
    Epoch seconds, or an empty result when the timestamp cannot be parsed.

    Testcases:
    No testcase defined.
    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
  • failureReason (scripted, public)

     <instance of nsmcp::AccessRecordParser[i]> failureReason line

    Classify why an access-log line could not be parsed. This diagnostic path runs only for rejected rows and distinguishes layout, timestamp and request failures.

    Parameters:
    line (required)
    Raw physical log line to inspect.
    Returns:
    The failure category: layout, timestamp or request.

    Testcases:
    No testcase defined.
    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
  • fields (scripted, public)

     <instance of nsmcp::AccessRecordParser[i]> fields peer identifier \
        user stamp request status bytes tail [ scannedFields ] \
        [ parsedTiming ]

    Normalize decoded access-log fields into a request record. Combine scanned metadata and optional C-parsed timing with the same validation used by Tcl scanning.

    Parameters:
    peer (required)
    Logged peer address, including the supported unknown-value sentinel.
    identifier (required)
    Complete log-thread identifier, or the raw identifier field when parsing access records.
    user (required)
    Logged user identifier used to derive authentication state.
    stamp (required)
    Original access-log timestamp field.
    request (required)
    Decoded HTTP request-line field.
    status (required)
    Decoded HTTP response status.
    bytes (required)
    Logged response-byte value, retaining the supported unknown-value sentinel.
    tail (required)
    Remaining access-log fields after status and response size.
    scannedFields (optional)
    Optional metadata already decoded by the C scanner.
    parsedTiming (optional)
    Optional timing dictionary already decoded by the C scanner.
    Returns:
    A normalized request dictionary, or an empty result for invalid fields.

    Testcases:
    No testcase defined.
    if {![regexp {^[1-5][0-9]{2}$} $status]} {
        return {}
    }
    set timestamp [string trim $stamp {[]}]
    set fields $scannedFields
    if {$fields eq {}} {
        set fields [regexp -all -inline {"(?:[^"\\]|\\.)*"} $tail]
    }
    set combined [expr {[llength $fields]>=2}]
    set agent {}
    set escapes [list {\\} \\ {\"} \" {\n} \n {\r} \r {\t} \t]
    if {$combined} {
        set agent [string map $escapes [string range [lindex $fields 1] 1 end-1]]
    }
    set request [string map $escapes $request]
    if {[string index $user 0] eq {"}} {
        set user [string map $escapes [string range $user 1 end-1]]
    }
    if {$parsedTiming eq {}} {
        set parsedTiming [:timingFields $timestamp $fields]
    }
    if {$parsedTiming eq {}} {
        return {}
    }
    set responseBytes 0
    if {$bytes ne "-"} {
        set responseBytes [scan $bytes %lld]
    }
    return [:requestFields $peer $identifier $user $timestamp $request  [scan $status %d] $responseBytes $combined $agent $parsedTiming]
  • indexTime (scripted, public)

     <instance of nsmcp::AccessRecordParser[i]> indexTime line

    Extract a bounded timestamp suitable for access-log indexing. Reject NUL-bearing or unsupported lines before deriving the record's index time.

    Parameters:
    line (required)
    Raw physical log line to inspect.
    Returns:
    The index timestamp, or an empty result for an unusable line.

    Testcases:
    No testcase defined.
    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]
  • parse (scripted, public)

     <instance of nsmcp::AccessRecordParser[i]> parse line

    Parse one physical access-log line into a request record. Validate the supported layout and delegate decoded fields to the common field normalizer.

    Parameters:
    line (required)
    Raw physical log line to inspect.
    Returns:
    A request dictionary, or an empty result for a rejected line.

    Testcases:
    No testcase defined.
    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 {}
    }
    return [:fields $peer $identifier $user $stamp $request $status $bytes $tail]
  • requestFields (scripted, public)

     <instance of nsmcp::AccessRecordParser[i]> requestFields peer \
        identifier user timestamp request status bytes combined agent \
        timing

    Build a request record from validated fields and timing. Derive authentication and request metadata while retaining explicit unknown-value representations.

    Parameters:
    peer (required)
    Logged peer address, including the supported unknown-value sentinel.
    identifier (required)
    Complete log-thread identifier, or the raw identifier field when parsing access records.
    user (required)
    Logged user identifier used to derive authentication state.
    timestamp (required)
    Parsed timestamp or timing base supplied by the parser.
    request (required)
    Decoded HTTP request-line field.
    status (required)
    Decoded HTTP response status.
    bytes (required)
    Logged response-byte value, retaining the supported unknown-value sentinel.
    combined (required)
    Whether the decoded access line uses the combined format.
    agent (required)
    Decoded User-Agent value.
    timing (required)
    Normalized request-timing dictionary.
    Returns:
    The normalized request dictionary.

    Testcases:
    No testcase defined.
    set authenticationState [expr {$user in {0 -} ? "anonymous" : ($user in {{} {[REDACTED]}} ? "unknown" : "authenticated")}]
    set result [dict create authenticationState $authenticationState timestamp $timestamp peerAddress $peer requestIdentifier {} pool unknown method unknown url {} status $status responseBytes $bytes timingSource completion]
    if {[::nsmcp::logRecordParser validRequestIdentifier $identifier]} {
        dict set result requestIdentifier $identifier
    }
    set result [dict merge $result [::nsmcp::accessRecordIdentity $identifier]]
    if {[dict get $result recordSource] eq "driver"} {
        # Request authentication was not performed by an application worker.
        dict set result authenticationState unknown
    }
    # 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
    }
    if {$combined && ${:timingColumn} in {-2 -1 2}} {
        dict set result userAgent $agent
    }
    set result [dict merge $result $timing]
    set available [expr {[dict get $timing timingSource] eq "start" && [dict get $result recordSource] ne "driver"}]
    dict set result phaseTimingAvailable $available
    dict set result phaseTimingReason [expr {$available ? "logged-worker-phases" : ([dict get $result recordSource] eq "driver" ? "driver-synthetic-phases" : "no-phase-counters")}]
    if {!$available} {
        foreach key {acceptTime queueTime filterTime runTime} {
            dict unset result $key
        }
    }
    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
  • timingMicroseconds (scripted, public)

     <instance of nsmcp::AccessRecordParser[i]> timingMicroseconds \
        timestamp start accept queue filter run

    Convert C-parsed request timing values to the parser's timing fields. Use supplied microsecond values directly, normalizing start time and phase durations to seconds.

    Parameters:
    timestamp (required)
    Parsed timestamp or timing base supplied by the parser.
    start (required)
    Request start timestamp in microseconds.
    accept (required)
    Accept-phase duration in microseconds.
    queue (required)
    Queue-phase duration in microseconds.
    filter (required)
    Filter-phase duration in microseconds, or the line-prefilter flag for nextLine.
    run (required)
    Run-phase duration in microseconds.
    Returns:
    A timing dictionary containing the available parsed phases.

    Testcases:
    No testcase defined.
    set phases {}
    if {$start ne {}} {
        set start [expr {$start / 1000000.0}]
        foreach value [list $accept $queue $filter $run] {
            lappend phases [expr {$value eq {} ? 0.0 : $value / 1000000.0}]
        }
    }
    return [:timingValues [string trim $timestamp {[]}] $start $phases]