Skip to content

Commit f901cdd

Browse files
Instance log and diagnostics files not written to configured location (#5248)
* Wrap `PopulateAppSettings` with error handling and ensure consistent initialization across all services * Add unit tests to verify appSettings population for ServiceControl, ServiceControl.Audit, and ServiceControl.Monitoring * Fix formatting errors --------- Co-authored-by: Mauro Servienti <[email protected]>
1 parent 583e3df commit f901cdd

File tree

7 files changed

+263
-23
lines changed

7 files changed

+263
-23
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
namespace ServiceControl.UnitTests;
2+
3+
using System;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
using System.Threading.Tasks;
8+
using NUnit.Framework;
9+
10+
[TestFixture]
11+
public class PopulateAppSettingsTests
12+
{
13+
[Test]
14+
public async Task Should_populate_appSettings_from_exe_config_file()
15+
{
16+
const string MagicValue = "7303A0AA-1003-4DC4-823B-4E8B2A35CF57";
17+
18+
var config = $"""
19+
<?xml version="1.0" encoding="utf-8"?>
20+
<configuration>
21+
<appSettings>
22+
<add key="ServiceControl.Audit/LogPath" value="{MagicValue}" />
23+
</appSettings>
24+
</configuration>
25+
""";
26+
27+
await File.WriteAllTextAsync("ServiceControl.Audit.exe.config", config);
28+
29+
var fileName = "ServiceControl.Audit";
30+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
31+
{
32+
fileName = "ServiceControl.Audit.exe";
33+
}
34+
35+
var startInfo = new ProcessStartInfo(fileName) { RedirectStandardOutput = true, UseShellExecute = false };
36+
37+
var p = Process.Start(startInfo);
38+
39+
if (p == null)
40+
{
41+
throw new Exception($"Failed to start {fileName}");
42+
}
43+
44+
var pathIsSet = false;
45+
46+
var outputTask = Task.Run(async () =>
47+
{
48+
while (!p.StandardOutput.EndOfStream)
49+
{
50+
var line = await p.StandardOutput.ReadLineAsync();
51+
52+
Console.WriteLine(line);
53+
54+
if (line.Contains($"Logging to {MagicValue}"))
55+
{
56+
pathIsSet = true;
57+
p.Kill(true);
58+
}
59+
}
60+
});
61+
62+
if (!p.WaitForExit(5000))
63+
{
64+
p.Kill(true);
65+
}
66+
67+
await outputTask;
68+
69+
Assert.That(pathIsSet, Is.True);
70+
}
71+
72+
[TearDown]
73+
public void TearDown() => File.Delete("ServiceControl.exe.config");
74+
}

src/ServiceControl.Audit/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
try
1313
{
14+
ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly());
15+
1416
var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace);
1517
LoggingConfigurator.ConfigureLogging(loggingSettings);
1618
logger = LoggerUtil.CreateStaticLogger(typeof(Program));
@@ -25,7 +27,6 @@
2527
return exitCode;
2628
}
2729

28-
ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly());
2930

3031
var arguments = new HostArguments(args);
3132

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace ServiceControl.Configuration
22
{
3+
using System;
34
using System.Configuration;
45
using System.IO;
56
using System.Linq;
@@ -12,27 +13,37 @@ public static class ExeConfiguration
1213
// This code reads in the exe.config files and adds all the values into the ConfigurationManager's collections.
1314
public static void PopulateAppSettings(Assembly assembly)
1415
{
15-
var location = Path.GetDirectoryName(assembly.Location);
16-
var assemblyName = Path.GetFileNameWithoutExtension(assembly.Location);
17-
var exeConfigPath = Path.Combine(location, $"{assemblyName}.exe.config");
18-
var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = exeConfigPath };
19-
var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
20-
21-
foreach (var key in configuration.AppSettings.Settings.AllKeys)
16+
try
2217
{
23-
ConfigurationManager.AppSettings.Set(key, configuration.AppSettings.Settings[key].Value);
18+
var location = Path.GetDirectoryName(assembly.Location);
19+
var assemblyName = Path.GetFileNameWithoutExtension(assembly.Location);
20+
var exeConfigPath = Path.Combine(location, $"{assemblyName}.exe.config");
21+
var fileMap = new ExeConfigurationFileMap
22+
{
23+
ExeConfigFilename = exeConfigPath
24+
};
25+
var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
26+
27+
foreach (var key in configuration.AppSettings.Settings.AllKeys)
28+
{
29+
ConfigurationManager.AppSettings.Set(key, configuration.AppSettings.Settings[key].Value);
30+
}
31+
32+
// The connection strings collection has had its read only flag set, so we need to clear it before we can add items to it
33+
UnsetCollectionReadonly(ConfigurationManager.ConnectionStrings);
34+
35+
foreach (var connectionStringSetting in configuration.ConnectionStrings.ConnectionStrings.Cast<ConnectionStringSettings>())
36+
{
37+
ConfigurationManager.ConnectionStrings.Add(connectionStringSetting);
38+
}
39+
40+
// Put the collection back into its previous state after we're done adding items to it
41+
SetCollectionReadOnly(ConfigurationManager.ConnectionStrings);
2442
}
25-
26-
// The connection strings collection has had its read only flag set, so we need to clear it before we can add items to it
27-
UnsetCollectionReadonly(ConfigurationManager.ConnectionStrings);
28-
29-
foreach (var connectionStringSetting in configuration.ConnectionStrings.ConnectionStrings.Cast<ConnectionStringSettings>())
43+
catch (Exception e)
3044
{
31-
ConfigurationManager.ConnectionStrings.Add(connectionStringSetting);
45+
throw new Exception("Failed to populate app settings.", e);
3246
}
33-
34-
// Put the collection back into its previous state after we're done adding items to it
35-
SetCollectionReadOnly(ConfigurationManager.ConnectionStrings);
3647
}
3748

3849
static void UnsetCollectionReadonly(ConfigurationElementCollection collection)
@@ -47,4 +58,4 @@ static void UnsetCollectionReadonly(ConfigurationElementCollection collection)
4758
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "SetReadOnly")]
4859
static extern void SetCollectionReadOnly(ConfigurationElementCollection collection);
4960
}
50-
}
61+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
namespace ServiceControl.UnitTests;
2+
3+
using System;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
using System.Threading.Tasks;
8+
using NUnit.Framework;
9+
10+
[TestFixture]
11+
public class PopulateAppSettingsTests
12+
{
13+
[Test]
14+
public async Task Should_populate_appSettings_from_exe_config_file()
15+
{
16+
const string MagicValue = "7303A0AA-1003-4DC4-823B-4E8B2A35CF57";
17+
18+
var config = $"""
19+
<?xml version="1.0" encoding="utf-8"?>
20+
<configuration>
21+
<appSettings>
22+
<add key="Monitoring/LogPath" value="{MagicValue}" />
23+
</appSettings>
24+
</configuration>
25+
""";
26+
27+
await File.WriteAllTextAsync("ServiceControl.Monitoring.exe.config", config);
28+
29+
var fileName = "ServiceControl.Monitoring";
30+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
31+
{
32+
fileName = "ServiceControl.Monitoring.exe";
33+
}
34+
35+
var startInfo = new ProcessStartInfo(fileName)
36+
{
37+
RedirectStandardOutput = true,
38+
UseShellExecute = false
39+
};
40+
41+
var p = Process.Start(startInfo);
42+
43+
if (p == null)
44+
{
45+
throw new Exception($"Failed to start {fileName}");
46+
}
47+
48+
var pathIsSet = false;
49+
50+
var outputTask = Task.Run(async () =>
51+
{
52+
while (!p.StandardOutput.EndOfStream)
53+
{
54+
var line = await p.StandardOutput.ReadLineAsync();
55+
56+
Console.WriteLine(line);
57+
58+
if (line.Contains($"Logging to {MagicValue}"))
59+
{
60+
pathIsSet = true;
61+
p.Kill(true);
62+
}
63+
}
64+
});
65+
66+
if (!p.WaitForExit(5000))
67+
{
68+
p.Kill(true);
69+
}
70+
71+
await outputTask;
72+
73+
Assert.That(pathIsSet, Is.True);
74+
}
75+
76+
[TearDown]
77+
public void TearDown() => File.Delete("ServiceControl.exe.config");
78+
}

src/ServiceControl.Monitoring/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
try
1111
{
12+
ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly());
13+
1214
var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace);
1315
LoggingConfigurator.ConfigureLogging(loggingSettings);
1416
logger = LoggerUtil.CreateStaticLogger(typeof(Program));
@@ -23,8 +25,6 @@
2325
return exitCode;
2426
}
2527

26-
ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly());
27-
2828
var arguments = new HostArguments(args);
2929

3030
var settings = new Settings(loggingSettings: loggingSettings);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
namespace ServiceControl.UnitTests;
2+
3+
using System;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
using System.Threading.Tasks;
8+
using NUnit.Framework;
9+
10+
[TestFixture]
11+
public class PopulateAppSettingsTests
12+
{
13+
[Test]
14+
public async Task Should_populate_appSettings_from_exe_config_file()
15+
{
16+
const string MagicValue = "7303A0AA-1003-4DC4-823B-4E8B2A35CF57";
17+
18+
var config = $"""
19+
<?xml version="1.0" encoding="utf-8"?>
20+
<configuration>
21+
<appSettings>
22+
<add key="ServiceControl/LogPath" value="{MagicValue}" />
23+
</appSettings>
24+
</configuration>
25+
""";
26+
27+
await File.WriteAllTextAsync("ServiceControl.exe.config", config);
28+
29+
var fileName = "ServiceControl";
30+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
31+
{
32+
fileName = "ServiceControl.exe";
33+
}
34+
35+
var startInfo = new ProcessStartInfo(fileName)
36+
{
37+
RedirectStandardOutput = true,
38+
UseShellExecute = false
39+
};
40+
41+
var p = Process.Start(startInfo);
42+
43+
if (p == null)
44+
{
45+
throw new Exception("Failed to start ServiceControl");
46+
}
47+
48+
var pathIsSet = false;
49+
50+
var outputTask = Task.Run(async () =>
51+
{
52+
while (!p.StandardOutput.EndOfStream)
53+
{
54+
var line = await p.StandardOutput.ReadLineAsync();
55+
56+
if (line.Contains($"Logging to {MagicValue}"))
57+
{
58+
pathIsSet = true;
59+
p.Kill(true);
60+
}
61+
}
62+
});
63+
64+
if (!p.WaitForExit(5000))
65+
{
66+
p.Kill(true);
67+
}
68+
69+
await outputTask;
70+
71+
Assert.That(pathIsSet, Is.True);
72+
}
73+
74+
[TearDown]
75+
public void TearDown() => File.Delete("ServiceControl.exe.config");
76+
}

src/ServiceControl/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
try
1313
{
14+
ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly());
15+
1416
var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace);
1517
LoggingConfigurator.ConfigureLogging(loggingSettings);
1618
logger = LoggerUtil.CreateStaticLogger(typeof(Program));
@@ -25,8 +27,6 @@
2527
return exitCode;
2628
}
2729

28-
ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly());
29-
3030
var arguments = new HostArguments(args);
3131

3232
if (arguments.Help)

0 commit comments

Comments
 (0)