Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions ASCOM Remote.sln
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Remote Server Key.snk = Remote Server Key.snk
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Remote Server.Tests", "Remote Server.Tests\Remote Server.Tests.csproj", "{145221E3-AFA6-4526-BD3F-FF6E53608185}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -48,6 +50,18 @@ Global
{15D5962B-2A5E-4B07-98B6-D4D4A888F9B7}.Release|x64.Build.0 = Release|x64
{15D5962B-2A5E-4B07-98B6-D4D4A888F9B7}.Release|x86.ActiveCfg = Release|x86
{15D5962B-2A5E-4B07-98B6-D4D4A888F9B7}.Release|x86.Build.0 = Release|x86
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Debug|Any CPU.Build.0 = Debug|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Debug|x64.ActiveCfg = Debug|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Debug|x64.Build.0 = Debug|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Debug|x86.ActiveCfg = Debug|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Debug|x86.Build.0 = Debug|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Release|Any CPU.ActiveCfg = Release|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Release|Any CPU.Build.0 = Release|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Release|x64.ActiveCfg = Release|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Release|x64.Build.0 = Release|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Release|x86.ActiveCfg = Release|Any CPU
{145221E3-AFA6-4526-BD3F-FF6E53608185}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
28 changes: 28 additions & 0 deletions Remote Server.Tests/Remote Server.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Remote Server\Remote Server.csproj" />
</ItemGroup>

</Project>
194 changes: 194 additions & 0 deletions Remote Server.Tests/TraceLoggerPlusTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using ASCOM.Remote;
using System.Text.RegularExpressions;
using Xunit;

namespace Remote_Server.Tests;

public sealed class TraceLoggerPlusTests : IDisposable
{
private readonly string testRoot = Path.Combine(
Path.GetTempPath(),
$"ASCOMRemoteTests-{Guid.NewGuid():N}");

[Fact]
public void LogMessage_RollsAutomaticallyNamedFileAtConfiguredSize()
{
TraceLoggerPlus logger = CreateLogger("SizeBounded");
try
{
logger.MaximumLogFileSizeBytes = 1;
logger.MaximumRetainedLogFiles = 10;

logger.LogMessage("Test", "First message");
logger.LogMessage("Test", "Second message");

string[] logFiles = FindLogs("SizeBounded");

Assert.Equal(2, logFiles.Length);
}
finally
{
logger.Dispose();
}
}

[Fact]
public void LogMessage_RetainsOnlyConfiguredFilesForSameLoggerType()
{
string yesterdayFolder = CreateDailyFolder(DateTime.Now.AddDays(-1));
string todayFolder = CreateDailyFolder(DateTime.Now);
string[] boundedLogs =
[
CreateOldLog(yesterdayFolder, "RetentionBounded", 0),
CreateOldLog(yesterdayFolder, "RetentionBounded", 1),
CreateOldLog(todayFolder, "RetentionBounded", 2),
CreateOldLog(todayFolder, "RetentionBounded", 3)
];
string otherLog = CreateOldLog(yesterdayFolder, "OtherLogger", 0);
string unrelatedFile = Path.Combine(todayFolder, "notes.txt");
string lookalikeFile = Path.Combine(
todayFolder,
"ASCOM.RetentionBounded.manual-backup.txt");
File.WriteAllText(unrelatedFile, "preserve me");
File.WriteAllText(lookalikeFile, "preserve me too");

TraceLoggerPlus logger = CreateLogger("RetentionBounded");
try
{
logger.MaximumRetainedLogFiles = 3;
logger.LogMessage("Test", "Current message");

Assert.Equal(3, FindAutomaticallyNamedLogs("RetentionBounded").Length);
Assert.True(File.Exists(otherLog));
Assert.True(File.Exists(unrelatedFile));
Assert.True(File.Exists(lookalikeFile));
Assert.False(File.Exists(boundedLogs[0]));
Assert.False(File.Exists(boundedLogs[1]));
}
finally
{
logger.Dispose();
}
}

[Fact]
public void LogMessage_RetriesRolloverAfterFileCreationFailure()
{
FailOnceTraceLoggerPlus logger = new(testRoot, "RecoverableRollover")
{
MaximumLogFileSizeBytes = 1,
MaximumRetainedLogFiles = 10
};

try
{
logger.LogMessage("Test", "First message");
logger.FailNextCreate = true;

Assert.Throws<ASCOM.DriverException>(() =>
logger.LogMessage("Test", "Message that triggers failure"));

logger.LogMessage("Test", "Message after recovery");

Assert.Equal(2, FindAutomaticallyNamedLogs("RecoverableRollover").Length);
}
finally
{
logger.Dispose();
}
}

[Fact]
public void LogMessage_DoesNotLimitGenericCallersByDefault()
{
string dailyFolder = CreateDailyFolder(DateTime.Now);
string oldLog = CreateOldLog(dailyFolder, "UnlimitedByDefault", 0);
TraceLoggerPlus logger = CreateLogger("UnlimitedByDefault");

try
{
logger.LogMessage("Test", "First message");
logger.LogMessage("Test", "Second message");

Assert.Equal(2, FindAutomaticallyNamedLogs("UnlimitedByDefault").Length);
Assert.True(File.Exists(oldLog));
}
finally
{
logger.Dispose();
}
}

public void Dispose()
{
if (Directory.Exists(testRoot))
{
Directory.Delete(testRoot, true);
}
}

private TraceLoggerPlus CreateLogger(string loggerType)
{
Directory.CreateDirectory(testRoot);
return new TraceLoggerPlus("", testRoot, loggerType, true);
}

private string CreateDailyFolder(DateTime date)
{
string dailyFolder = Path.Combine(
testRoot,
$"Logs {date:yyyy-MM-dd}");
Directory.CreateDirectory(dailyFolder);
return dailyFolder;
}

private static string CreateOldLog(
string dailyFolder,
string loggerType,
int index)
{
string path = Path.Combine(
dailyFolder,
$"ASCOM.{loggerType}.0000.00000{index}.txt");
File.WriteAllText(path, $"old log {index}");
File.SetLastWriteTimeUtc(path, DateTime.UtcNow.AddMinutes(index - 10));
return path;
}

private string[] FindLogs(string loggerType)
{
return Directory.GetFiles(
testRoot,
$"ASCOM.{loggerType}.*.txt",
SearchOption.AllDirectories);
}

private string[] FindAutomaticallyNamedLogs(string loggerType)
{
Regex automaticFileNamePattern = new(
$"^ASCOM\\.{Regex.Escape(loggerType)}\\.\\d{{4}}\\.\\d{{6,7}}\\.txt$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

return FindLogs(loggerType)
.Where(path => automaticFileNamePattern.IsMatch(Path.GetFileName(path)))
.ToArray();
}

private sealed class FailOnceTraceLoggerPlus(
string logRoot,
string loggerType) : TraceLoggerPlus("", logRoot, loggerType, true)
{
public bool FailNextCreate { get; set; }

protected override StreamWriter CreateStreamWriter(string filePath)
{
if (FailNextCreate)
{
FailNextCreate = false;
throw new IOException("Simulated log file creation failure.");
}

return base.CreateStreamWriter(filePath);
}
}
}
22 changes: 16 additions & 6 deletions Remote Server/ServerForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public partial class ServerForm : Form
internal const string SERVER_ACCESS_LOG_PROFILENAME = "Server Access Log Enabled"; internal const bool SERVER_ACCESS_LOG_DEFAULT = true;
internal const string SERVER_TRACE_LEVEL_PROFILENAME = "Server Trace Level"; internal const bool SERVER_TRACE_LEVEL_DEFAULT = true;
internal const string SERVER_DEBUG_TRACE_PROFILENAME = "Server Include Debug Trace"; internal const bool SERVER_DEBUG_TRACE_DEFAULT = false;
internal const long SERVER_LOG_MAXIMUM_FILE_SIZE_BYTES = 50L * 1024L * 1024L;
internal const int SERVER_LOG_MAXIMUM_RETAINED_FILES = 10;
internal const string SERVER_IPADDRESS_PROFILENAME = "Server IP Address"; internal const string SERVER_IPADDRESS_DEFAULT = SharedConstants.LOCALHOST_ADDRESS_IPV4;
internal const string SERVER_PORTNUMBER_PROFILENAME = "Server Port Number"; internal const decimal SERVER_PORTNUMBER_DEFAULT = 11111;
internal const string SERVER_AUTOCONNECT_PROFILENAME = "Server Auto Connect"; internal const bool SERVER_AUTOCONNECT_DEFAULT = true;
Expand Down Expand Up @@ -359,7 +361,9 @@ public ServerForm()
{
LogFilePath = TraceFolder, // Set the trace folder to the user specified value
Enabled = TraceState, // Enable the log if required
UseUtcTime = UseUtcTimeInLogs
UseUtcTime = UseUtcTimeInLogs,
MaximumLogFileSizeBytes = SERVER_LOG_MAXIMUM_FILE_SIZE_BYTES,
MaximumRetainedLogFiles = SERVER_LOG_MAXIMUM_RETAINED_FILES
};

LogMessage(0, 0, 0, "New", $"Remote Server Version {Updates.AscomRemoteVersionDisplayString}, Started on {DateTime.Now:dddd d MMMM yyyy HH: mm:ss}");
Expand All @@ -372,7 +376,9 @@ public ServerForm()
{
LogFilePath = TraceFolder, // Set the trace folder to the user specified value
Enabled = AccessLogEnabled,
UseUtcTime = UseUtcTimeInLogs
UseUtcTime = UseUtcTimeInLogs,
MaximumLogFileSizeBytes = SERVER_LOG_MAXIMUM_FILE_SIZE_BYTES,
MaximumRetainedLogFiles = SERVER_LOG_MAXIMUM_RETAINED_FILES
};

LogMessage(0, 0, 0, "New", "Setting screen log check boxes"); // Must be done before enabling event handlers!
Expand Down Expand Up @@ -1467,11 +1473,13 @@ private static void CheckWhetherNewLogRequired(uint clientID, uint clientTransac
TL = null;

// Start a new logger
TL = new TraceLoggerPlus("", SERVER_TRACELOGGER_NAME)
TL = new TraceLoggerPlus("", TraceFolder, SERVER_TRACELOGGER_NAME, true)
{
LogFilePath = TraceFolder,
Enabled = true, // Enable the trace logger
IpAddressTraceState = LogClientIPAddress // Set the current state of the "include client IP address in trace lines" flag
IpAddressTraceState = LogClientIPAddress, // Set the current state of the "include client IP address in trace lines" flag
MaximumLogFileSizeBytes = SERVER_LOG_MAXIMUM_FILE_SIZE_BYTES,
MaximumRetainedLogFiles = SERVER_LOG_MAXIMUM_RETAINED_FILES
};

TL.LogMessage(clientID, clientTransactionID, serverTransactionID, "StartOfDay", "Opening a new log because a new day has started. " + now.ToString("dddd d MMMM yyyy HH:mm:ss"));
Expand Down Expand Up @@ -2744,11 +2752,13 @@ private void ProcessRestRequest(HttpListenerContext context)
AccessLog = null;

// Start a new logger
AccessLog = new TraceLoggerPlus("", ACCESSLOG_TRACELOGGER_NAME)
AccessLog = new TraceLoggerPlus("", TraceFolder, ACCESSLOG_TRACELOGGER_NAME, true)
{
LogFilePath = TraceFolder,
Enabled = true, // Enable the trace logger
IpAddressTraceState = LogClientIPAddress // Set the current state of the "include client IP address in trace lines" flag
IpAddressTraceState = LogClientIPAddress, // Set the current state of the "include client IP address in trace lines" flag
MaximumLogFileSizeBytes = SERVER_LOG_MAXIMUM_FILE_SIZE_BYTES,
MaximumRetainedLogFiles = SERVER_LOG_MAXIMUM_RETAINED_FILES
};

AccessLog.LogMessage(clientID, clientTransactionID, serverTransactionID, "StartOfDay", "Opening a new log because a new day has started. " + now.ToString("dddd d MMMM yyyy HH:mm:ss"));
Expand Down
Loading