This is an automated email from the ASF dual-hosted git repository. FreeAndNil pushed a commit to branch Feature/162-quiet-app-settings-in-a-native-host in repository https://gitbox.apache.org/repos/asf/logging-log4net.git
commit 13d931ef36ecc607786befc4c4cee2226a52ffc8 Author: Jan Friedrich <[email protected]> AuthorDate: Wed Aug 19 22:12:52 2026 +0200 Recognise a native host as having no configuration system (#162) In a process that hosts the runtime natively there is no entry assembly for the configuration system to derive the config file path from, so reading an application setting fails in ClientConfigPaths with a PlatformNotSupportedException wrapped in a ConfigurationErrorsException. That matched none of the shapes IsMissingConfigurationSystem recognised, so log4net blamed the user's config file and repeated the report for every setting the static constructors read: seven log4net:ERROR blocks with a stack trace each, at startup, in a well configured application. A PlatformNotSupportedException anywhere in the inner exception chain is now treated as an absent configuration system, the same as the Native AOT case. A malformed config file cannot produce one, so unlike the FileNotFoundException and TypeLoadException cases it needs no check on which assembly it came from. The failure is logged once at debug level and application settings come from environment variables. Verified on Windows in a C++ host of the CoreCLR built for the purpose, in a host that loads the runtime through hostfxr, and in powershell.exe loading a netstandard2.0 build output: seven error blocks before, none after, and exactly one debug line with log4net.Internal.Debug on. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- .../162-quiet-app-settings-in-a-native-host.xml | 14 ++++++++++++++ src/log4net.Tests/Util/SystemInfoTest.cs | 13 +++++++++++++ src/log4net/Util/SystemInfo.cs | 22 +++++++++++++++------- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/changelog/3.4.1/162-quiet-app-settings-in-a-native-host.xml b/src/changelog/3.4.1/162-quiet-app-settings-in-a-native-host.xml new file mode 100644 index 00000000..9f995548 --- /dev/null +++ b/src/changelog/3.4.1/162-quiet-app-settings-in-a-native-host.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="https://logging.apache.org/xml/ns" + xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="fixed"> + <issue id="162" link="https://github.com/apache/logging-log4net/issues/162"/> + <description format="asciidoc">Stop reporting `log4net:ERROR Exception while reading ConfigurationSettings` + in a process that hosts the runtime natively, such as `powershell.exe` or a C++ host of the CoreCLR. + There is no entry assembly there for the configuration system to derive the config file path from, so + it fails with `PlatformNotSupportedException` before any config file is read. That is now recognised + as an absent configuration system, the same as under Native AOT: it is logged once at debug level and + application settings are read from environment variables instead + (reported by @viktorgobbi, fixed by @FreeAndNil)</description> +</entry> diff --git a/src/log4net.Tests/Util/SystemInfoTest.cs b/src/log4net.Tests/Util/SystemInfoTest.cs index fbf544bd..ebf3a72f 100644 --- a/src/log4net.Tests/Util/SystemInfoTest.cs +++ b/src/log4net.Tests/Util/SystemInfoTest.cs @@ -274,6 +274,19 @@ public void MissingApplicationAssemblyIsNotTreatedAsAMissingConfigurationSystem( new ConfigurationErrorsException("An error occurred creating the configuration section handler", new FileNotFoundException("Could not load file or assembly", "Contoso.SectionHandlers"))), Is.False); + /// <summary> + /// A process that hosts the runtime natively has no entry assembly for the configuration system + /// to derive the config file path from, so it cannot read a config file whatever state that file + /// is in. That is recognised and the environment stands in, rather than being reported as a + /// malformed file on every setting log4net reads. + /// </summary> + [Test] + public void NativeHostExceptionIsRecognised() + => Assert.That(IsMissingConfigurationSystem( + new ConfigurationErrorsException("Configuration system failed to initialize", + new PlatformNotSupportedException("Operation is not supported on this platform."))), + Is.True); + private static bool IsMissingConfigurationSystem(Exception exception) { MethodInfo method = typeof(SystemInfo).GetMethod("IsMissingConfigurationSystem", BindingFlags.Static | BindingFlags.NonPublic) diff --git a/src/log4net/Util/SystemInfo.cs b/src/log4net/Util/SystemInfo.cs index eb3c656b..f71876d7 100644 --- a/src/log4net/Util/SystemInfo.cs +++ b/src/log4net/Util/SystemInfo.cs @@ -721,12 +721,13 @@ public static bool TryParse(string s, out short val) { if (IsMissingConfigurationSystem(e)) { - // There is no configuration system to read - Native AOT trims System.Configuration away. - // That is a property of the runtime rather than a fault, so it is not reported as an - // error, and the environment stands in for the config file as it does on Android. + // There is no configuration system to read - Native AOT trims System.Configuration away, + // and a native process hosting the runtime has no entry assembly for it to derive the + // config file path from. That is a property of the host rather than a fault, so it is not + // reported as an error, and the environment stands in for the config file as on Android. _configurationSystemUnavailable = true; LogLog.Debug(_declaringType, - "No configuration system on this runtime. Using environment variables for application settings.", e); + "No configuration system on this host. Using environment variables for application settings.", e); return Environment.GetEnvironmentVariable(key); } @@ -746,10 +747,12 @@ public static bool TryParse(string s, out short val) /// <returns><see langword="true"/> if the configuration system itself is unavailable</returns> /// <remarks> /// <para> - /// The inner exceptions have to be walked, because Native AOT surfaces this as a + /// The inner exceptions have to be walked, because the runtime surfaces both cases as a /// <see cref="ConfigurationErrorsException"/> - the very type a malformed file produces. What - /// distinguishes it is further down the chain: a <see cref="MissingMethodException"/> for - /// <c>ClientConfigurationHost</c>, whose constructor the trimmer removed. + /// distinguishes them is further down the chain: under Native AOT a + /// <see cref="MissingMethodException"/> for <c>ClientConfigurationHost</c>, whose constructor the + /// trimmer removed, and in a native process hosting the runtime a + /// <see cref="PlatformNotSupportedException"/> from <c>ClientConfigPaths</c>. /// </para> /// <para> /// An unrecognized failure is treated as a configuration file problem, which is the safer way @@ -773,6 +776,11 @@ private static bool IsMissingConfigurationSystem(Exception? exception) { switch (exception) { + // The configuration system cannot work out where the config file is, because there is no + // entry assembly to derive its path from. That is what a native process hosting the runtime + // looks like, and no config file can be read there however well formed it is. A malformed + // file never produces this, so it needs no check on which assembly it came from. + case PlatformNotSupportedException: case FileNotFoundException { FileName: string fileName } when IsConfigurationSystem(fileName): case TypeLoadException { TypeName: string typeName }
