@@ -14,14 +14,20 @@ catch {
1414# These script-scoped values act as the main tuning points for future maintenance.
1515# Keeping them together makes it easier to adjust behavior without searching the file.
1616$script :AppName = ' GW Router Logger'
17+ $script :Version = ' 1.0.0'
1718$script :MaxCompressedBytes = 100 MB
1819$script :ActiveLogRotateBytes = 5 MB
1920$script :ActiveLogRotateMinutes = 60
2021$script :RecentEventsMax = 12
2122$script :StatusRefreshMilliseconds = 2000
2223$script :DefaultUdpPort = 514
2324$script :DefaultTcpPort = 514
25+ $script :DefaultResolveHostNames = $false
26+ $script :DnsLookupTimeoutMilliseconds = 250
27+ $script :SourceNameCacheTtlMinutes = 30
28+ $script :TcpClientIdleTimeoutMinutes = 15
2429$script :LastSettings = $null
30+ $script :SourceNameCache = @ {}
2531
2632function Write-Rule {
2733 param (
@@ -59,6 +65,7 @@ function Show-StartupSplash {
5965 Write-UiLine ' (_______)(_______)|/ \|\_______)|_/ \/|/ \__/(_______/' Cyan
6066 Write-Rule - Width 78 - Color DarkCyan
6167 Write-UiLine ' GW ROUTER LOGGER' White
68+ Write-UiLine (' Version {0}' -f $script :Version ) DarkGray
6269 Write-UiLine ' Residential-friendly PowerShell syslog collector' DarkGray
6370 Write-Host
6471 Write-LabelValue - Label ' Mode' - Value ' Menu-driven foreground listener' - ValueColor Gray
@@ -511,7 +518,7 @@ function Get-RunSettings {
511518 $defaultBind = $null
512519 $defaultUdp = $script :DefaultUdpPort
513520 $defaultTcp = $script :DefaultTcpPort
514- $defaultLookup = $true
521+ $defaultLookup = $script :DefaultResolveHostNames
515522 $defaultLogPath = Join-Path - Path (Get-ScriptRootPath ) - ChildPath ' GW-ROUTER-LOGS'
516523
517524 if ($script :LastSettings ) {
@@ -635,18 +642,18 @@ function Rotate-And-CompressLog {
635642 )
636643
637644 if (-not (Test-Path - LiteralPath $LogFilePath )) {
638- return
645+ return $false
639646 }
640647
641648 # Rotate active logs on either size or age so quiet systems still archive regularly
642649 # and busy systems do not let a single active file grow too large.
643650 $fileInfo = Get-Item - LiteralPath $LogFilePath
644- $ageMinutes = ((Get-Date ) - $fileInfo.LastWriteTime ).TotalMinutes
651+ $ageMinutes = ((Get-Date ).ToUniversalTime() - $fileInfo.CreationTimeUtc ).TotalMinutes
645652 $shouldRotateBySize = $fileInfo.Length -ge $script :ActiveLogRotateBytes
646653 $shouldRotateByAge = $ageMinutes -ge $script :ActiveLogRotateMinutes
647654
648655 if (-not $shouldRotateBySize -and -not $shouldRotateByAge ) {
649- return
656+ return $false
650657 }
651658
652659 Ensure- Directory - Path $ArchiveDirectory | Out-Null
@@ -669,12 +676,14 @@ function Rotate-And-CompressLog {
669676 if ($State ) {
670677 Add-RecentEvent - State $State - Message (' Archived {0}' -f [IO.Path ]::GetFileName($zipPath ))
671678 }
679+ return $true
672680 }
673681 catch {
674682 if ($State ) {
675683 $State.LastError = ' Log rotation error: ' + $_.Exception.Message
676684 Add-RecentEvent - State $State - Message $State.LastError
677685 }
686+ return $false
678687 }
679688}
680689
@@ -734,8 +743,10 @@ function Write-LogRecord {
734743 $line = ' {0} [{1}] {2}' -f $timestamp , $Category.ToUpperInvariant (), $Message
735744 try {
736745 Add-Content - LiteralPath $targetPath - Value $line - Encoding UTF8
737- Rotate- And- CompressLog - LogFilePath $targetPath - ArchiveDirectory $archiveRoot - State $State
738- Enforce- CompressedArchiveCap - RootPath $Settings.LogRoot - State $State
746+ $rotated = Rotate- And- CompressLog - LogFilePath $targetPath - ArchiveDirectory $archiveRoot - State $State
747+ if ($rotated ) {
748+ Enforce- CompressedArchiveCap - RootPath $Settings.LogRoot - State $State
749+ }
739750 }
740751 catch {
741752 if ($State ) {
@@ -761,20 +772,52 @@ function Resolve-SourceIdentity {
761772 [bool ] $ResolveHostNames
762773 )
763774
775+ $nowUtc = (Get-Date ).ToUniversalTime()
776+ $cacheKey = ' {0}|{1}' -f $Address , $ResolveHostNames
777+ if ($script :SourceNameCache.ContainsKey ($cacheKey )) {
778+ $cached = $script :SourceNameCache [$cacheKey ]
779+ if ($cached.ExpiresUtc -gt $nowUtc ) {
780+ return $cached.Name
781+ }
782+ }
783+
764784 if (-not $ResolveHostNames ) {
785+ $script :SourceNameCache [$cacheKey ] = @ {
786+ Name = $Address
787+ ExpiresUtc = $nowUtc.AddMinutes ($script :SourceNameCacheTtlMinutes )
788+ }
765789 return $Address
766790 }
767791
792+ $resolvedName = $Address
793+ $asyncResult = $null
768794 try {
769- $entry = [System.Net.Dns ]::GetHostEntry($Address )
770- if ($entry.HostName ) {
771- return ' {0}_{1}' -f $entry.HostName , $Address
795+ $asyncResult = [System.Net.Dns ]::BeginGetHostEntry($Address , $null , $null )
796+ $completed = $asyncResult.AsyncWaitHandle.WaitOne ($script :DnsLookupTimeoutMilliseconds , $false )
797+ if ($completed ) {
798+ $entry = [System.Net.Dns ]::EndGetHostEntry($asyncResult )
799+ if ($entry.HostName ) {
800+ $resolvedName = ' {0}_{1}' -f $entry.HostName , $Address
801+ }
772802 }
773803 }
774804 catch {
775805 }
806+ finally {
807+ if ($asyncResult ) {
808+ try {
809+ $asyncResult.AsyncWaitHandle.Close ()
810+ }
811+ catch {
812+ }
813+ }
814+ }
776815
777- return $Address
816+ $script :SourceNameCache [$cacheKey ] = @ {
817+ Name = $resolvedName
818+ ExpiresUtc = $nowUtc.AddMinutes ($script :SourceNameCacheTtlMinutes )
819+ }
820+ return $resolvedName
778821}
779822
780823function Test-IsReservedFileStem {
@@ -1009,6 +1052,26 @@ function Show-NoLogGuidance {
10091052 Pause- ForUser - Message ' Press Enter to return to the listener'
10101053}
10111054
1055+ function Test-TcpClientClosed {
1056+ param ([System.Net.Sockets.TcpClient ] $TcpClient )
1057+
1058+ try {
1059+ if (-not $TcpClient.Connected ) {
1060+ return $true
1061+ }
1062+
1063+ $socket = $TcpClient.Client
1064+ if ($socket.Poll (0 , [System.Net.Sockets.SelectMode ]::SelectRead) -and $socket.Available -eq 0 ) {
1065+ return $true
1066+ }
1067+
1068+ return $false
1069+ }
1070+ catch {
1071+ return $true
1072+ }
1073+ }
1074+
10121075function Get-CompletedTcpMessages {
10131076 param ([string ] $Buffer )
10141077
@@ -1155,6 +1218,7 @@ function Start-LogServer {
11551218 Resolve-PreferredLogRoot - Settings $Settings
11561219 $state = New-RuntimeState - Settings $Settings
11571220
1221+ Enforce- CompressedArchiveCap - RootPath $Settings.LogRoot - State $state
11581222 Write-ServerEvent - Settings $Settings - State $state - Message ' Server startup requested.'
11591223
11601224 if ($Settings.UdpPort -gt 0 -and -not (Test-PortAvailable - Address $Settings.BindAddress - Port $Settings.UdpPort - Protocol ' UDP' )) {
@@ -1235,6 +1299,7 @@ function Start-LogServer {
12351299 Stream = $accepted.GetStream ()
12361300 Buffer = ' '
12371301 Address = ([string ] $accepted.Client.RemoteEndPoint ).Split(' :' )[0 ]
1302+ LastActivity = Get-Date
12381303 }
12391304 [void ] $tcpClients.Add ($clientState )
12401305 $connectMessage = ' TCP client connected: {0}' -f $clientState.Address
@@ -1251,7 +1316,19 @@ function Start-LogServer {
12511316 for ($index = $tcpClients.Count - 1 ; $index -ge 0 ; $index -- ) {
12521317 $clientState = $tcpClients [$index ]
12531318 try {
1254- if (-not $clientState.Client.Connected ) {
1319+ $idleMinutes = ((Get-Date ) - $clientState.LastActivity ).TotalMinutes
1320+ if ($idleMinutes -ge $script :TcpClientIdleTimeoutMinutes ) {
1321+ if ($clientState.Buffer ) {
1322+ Register-ReceivedMessage - Settings $Settings - State $state - Protocol ' TCP' - Address $clientState.Address - RawMessage $clientState.Buffer
1323+ }
1324+ Add-RecentEvent - State $state - Message (' TCP client idle timeout: {0}' -f $clientState.Address )
1325+ $clientState.Stream.Dispose ()
1326+ $clientState.Client.Dispose ()
1327+ $tcpClients.RemoveAt ($index )
1328+ continue
1329+ }
1330+
1331+ if (Test-TcpClientClosed - TcpClient $clientState.Client ) {
12551332 if ($clientState.Buffer ) {
12561333 Register-ReceivedMessage - Settings $Settings - State $state - Protocol ' TCP' - Address $clientState.Address - RawMessage $clientState.Buffer
12571334 }
@@ -1274,6 +1351,7 @@ function Start-LogServer {
12741351 continue
12751352 }
12761353
1354+ $clientState.LastActivity = Get-Date
12771355 $clientState.Buffer += [Text.Encoding ]::UTF8.GetString($readBuffer , 0 , $bytesRead )
12781356 $parsed = Get-CompletedTcpMessages - Buffer $clientState.Buffer
12791357 foreach ($message in $parsed.Messages ) {
@@ -1364,6 +1442,11 @@ function Show-EnvironmentSummary {
13641442
13651443function Show-DefaultSettingsMenu {
13661444 while ($true ) {
1445+ $hostnameLookupDisplay = ' Disabled'
1446+ if ($script :DefaultResolveHostNames ) {
1447+ $hostnameLookupDisplay = ' Enabled'
1448+ }
1449+
13671450 Clear-Host
13681451 Write-TitleBlock - Title ' GW ROUTER LOGGER' - Subtitle ' Change defaults'
13691452 Write-Host
@@ -1373,7 +1456,10 @@ function Show-DefaultSettingsMenu {
13731456 Write-Host (' 4. Active log rotate size: {0} MB' -f ([int ]($script :ActiveLogRotateBytes / 1 MB )))
13741457 Write-Host (' 5. Active log rotate age: {0} minutes' -f $script :ActiveLogRotateMinutes )
13751458 Write-Host (' 6. Recent event lines shown: {0}' -f $script :RecentEventsMax )
1376- Write-Host ' 7. Return to main menu'
1459+ Write-Host (' 7. Hostname lookup default: {0}' -f $hostnameLookupDisplay )
1460+ Write-Host (' 8. DNS lookup timeout: {0} ms' -f $script :DnsLookupTimeoutMilliseconds )
1461+ Write-Host (' 9. TCP idle timeout: {0} minutes' -f $script :TcpClientIdleTimeoutMinutes )
1462+ Write-Host ' 10. Return to main menu'
13771463 Write-Host
13781464
13791465 $selection = Read-Host ' Choose a setting to change'
@@ -1399,6 +1485,15 @@ function Show-DefaultSettingsMenu {
13991485 $script :RecentEventsMax = Read-ValidatedPositiveInt - Prompt ' Recent event lines shown' - Default $script :RecentEventsMax - Minimum 3
14001486 }
14011487 ' 7' {
1488+ $script :DefaultResolveHostNames = Read-BooleanChoice - Prompt ' Enable hostname lookup by default' - Default $script :DefaultResolveHostNames
1489+ }
1490+ ' 8' {
1491+ $script :DnsLookupTimeoutMilliseconds = Read-ValidatedPositiveInt - Prompt ' DNS lookup timeout in milliseconds' - Default $script :DnsLookupTimeoutMilliseconds - Minimum 50
1492+ }
1493+ ' 9' {
1494+ $script :TcpClientIdleTimeoutMinutes = Read-ValidatedPositiveInt - Prompt ' TCP idle timeout in minutes' - Default $script :TcpClientIdleTimeoutMinutes - Minimum 1
1495+ }
1496+ ' 10' {
14021497 return
14031498 }
14041499 default {
0 commit comments