Skip to content

Architecture & Internals

Component Overview

┌─────────────────────────────────────────────────┐
│              aaLogForwarder (GUI)                │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌───────────────┐  │
│  │ Settings  │  │ Polling  │  │   Network     │  │
│  │ Manager   │  │ Loop     │  │   Sender      │  │
│  └──────────┘  └────┬─────┘  └───────┬───────┘  │
│                     │                │           │
└─────────────────────┼────────────────┼───────────┘
                      │                │
                      ▼                ▼
              ┌──────────────┐   ┌──────────┐
              │ aaLogReader  │   │ Syslog   │
              │ (library)    │   │ Server   │
              └──────┬───────┘   └──────────┘
              ┌──────────────┐
              │ .aaLog Files │
              │ + Cache File │
              └──────────────┘

The system has three layers:

  1. aaLogForwarder -- WinForms GUI that drives the polling loop, manages settings, and sends records over the network
  2. aaLogReader -- Library that reads binary .aaLog files and tracks position via a cache file
  3. Binary log files -- Written by Archestra/System Platform on disk

Polling Loop

The forwarder runs a continuous polling loop on a background thread. Each cycle:

  1. Creates a new aaLogReader instance
  2. Calls GetUnreadRecords() to fetch new log records
  3. Formats each record (RFC 5424, RFC 3164, or KVP)
  4. Sends formatted records over TCP or UDP
  5. Waits for the configured polling interval
  6. Repeats
 ┌──────────────────────────────────────────────┐
 │                                              │
 ▼                                              │
Read Records ──► Format ──► Send ──► Wait ──────┘
     │                        │
     │ (on failure)           │ (on failure)
     ▼                        ▼
  Log Error              Backoff + Retry

Polling Interval

The interval between cycles is configurable from 500 ms to 60,000 ms (default: 1,000 ms). The interval is read from the UI control at the start of each cycle, so it can be changed while running.

First-Iteration Behavior

When Send Existing Logs is enabled, the first polling cycle calls:

GetUnreadRecords(maximumMessages: ulong.MaxValue, IgnoreCacheFile: true)

This reads all records from disk, ignoring the cache file. Subsequent cycles call GetUnreadRecords() normally, reading only new records.


Error Handling & Retry

Exception Categories

The polling loop catches specific exception types:

Exception Meaning Action
OperationCanceledException User clicked Stop Propagate (exit loop)
SocketException Network unreachable, connection refused Retry with backoff
AuthenticationException TLS handshake failed Retry with backoff
IOException Connection reset, broken pipe Retry with backoff
Exception Anything else Retry with backoff

Exponential Backoff

On failure, the wait time increases exponentially:

backoff = min(interval * 2^(failureCount - 1), 30000)
Consecutive Failures Backoff (1s interval)
1 1,000 ms
2 2,000 ms
3 4,000 ms
4 8,000 ms
5 16,000 ms
6+ 30,000 ms (cap)

The backoff resets to zero after any successful cycle.

Auto-Stop

After 10 consecutive failures, the forwarder stops automatically. The status bar shows:

STOPPED - 10 consecutive failures

Check the connection settings and click Start to retry.


Network Sending

TCP

For TCP connections, the forwarder:

  1. Opens a new TcpClient for each polling cycle
  2. Optionally wraps the stream in SslStream for TLS
  3. Sends all records in the batch sequentially
  4. Flushes and closes the connection

TCP uses RFC 6587 octet-counting framing for RFC 5424 and RFC 3164 formats:

<byte-count> <syslog-message>

KVP format uses newline-delimited framing.

UDP

For UDP connections, each record is sent as an independent datagram. There is no connection state, no TLS, and no delivery guarantee.


Log Record Reading

File Discovery

The aaLogReader library finds .aaLog files by resolving the log directory in this order:

  1. OptionsStruct.LogDirectory (if set programmatically)
  2. Registry: HKLM\SOFTWARE\Wow6432Node\ArchestrA\Framework\LoggerLogDir
  3. %ProgramData%\ArchestrA\LogFiles (if the directory exists)
  4. Fallback: C:\ProgramData\ArchestrA\LogFiles

File Format

Each .aaLog file contains:

  • A file header with metadata (message count, time range, computer name, offsets)
  • A sequence of log records with backward and forward linkage

Records are stored in a binary format with fixed-size numeric fields and variable-length Unicode strings. See Binary Log Format for byte-level details.

File Sharing

The reader opens files with FileShare.ReadWrite, which allows reading active log files that Archestra is still writing to. This is critical for real-time forwarding.

Cache File

The cache file stores the last-read LogRecord as JSON. On each call to GetUnreadRecords():

  1. Read the cache file to get the last message number
  2. Scan forward from that position in the current (and newer) log files
  3. Return the new records
  4. Update the cache file with the latest record

The cache file is stored in the same directory as the .aaLog files, named after the calling process (e.g., aaLogGUITester-aaLogReaderCache).


Threading Model

aaLogForwarder

Thread Role
UI thread Handles user input, updates controls
Background task Runs PollingLoopAsync via Task.Run

The polling loop accesses UI controls using Control.Invoke() / InvokeRequired for thread safety. The CancellationTokenSource coordinates shutdown between the UI thread and the background task.

aaLogReader

The aaLogReader class is not thread-safe. It maintains shared mutable state:

  • _fileStream -- the currently open log file
  • _currentLogHeader -- parsed header of the current file
  • _lastRecordRead -- the most recently read record

Each polling cycle creates a new aaLogReader instance (wrapped in using), so there is no shared state between cycles.


Message Formatting Pipeline

Each log record passes through this pipeline before being sent:

LogRecord
Sanitize (flatten + truncate)
Format (RFC5424 / RFC3164 / KVP)
Frame (octet-counting or newline)
Encode (UTF-8)
Send (TCP or UDP)

Sanitization

Sanitization is applied at the LogRecord level via static properties:

  • Flatten: Controlled by LogRecord.FlattenMessages. Replaces \r\n, \n, \r with |.
  • Truncate: Controlled by LogRecord.MessageTruncateLength. Cuts at the character limit and appends ....

These are set once when Start is clicked and apply to all records in the session.

Severity Mapping

The LogFlag string from each record is mapped to a numeric syslog severity:

LogFlag Severity
critical 2 (Critical)
error, failure 3 (Error)
warning 4 (Warning)
success, info 6 (Informational)
debug 7 (Debug)
(other) 6 (Informational)

The mapping is case-insensitive.