FreeAndNil commented on issue #233:
URL: 
https://github.com/apache/logging-log4net/issues/233#issuecomment-5197198339

   ### Reproduction
   
   Reproduced on .NET 10, `PublishAot=true`, `linux-x64`, against `log4net` 
built from `master`:
   
   | API | JIT | Native AOT |
   |---|---|---|
   | `Assembly.GetCallingAssembly()` | ok | **throws 
`PlatformNotSupportedException`** |
   | `Assembly.GetExecutingAssembly()` | ok | ok |
   | `Assembly.GetEntryAssembly()` | ok | ok |
   | `typeof(X).Assembly` | ok | ok |
   | `new StackTrace().GetFrame(1).GetMethod()` | ok | **returns `null`** |
   
   So there is no stack-walking fallback available under AOT; the calling 
assembly is simply not
   recoverable at runtime.
   
   `Assembly.GetCallingAssembly()` is called from **18 call sites** in 
`src/log4net`, not just
   `GetLogger`:
   
   - `LogManager` — 9 (`Exists`, `GetCurrentLoggers`, `GetLogger(string)`, 
`GetLogger(Type)`,
     `ShutdownRepository`, `ResetConfiguration`, `GetRepository`, 
`CreateRepository(Type)`, `Flush`)
   - `XmlConfigurator` — 6
   - `BasicConfigurator` — 2
   - `SystemInfo.GetTypeFromString(string, bool, bool)` — 1
   
   ### Why new target frameworks alone will not fix it
   
   Adding `net6.0`/`net8.0` TFMs plus `#if` does not solve this. AOT-ness is a 
**publish mode**, not a
   target framework - the same `net8.0` assembly runs both JIT-compiled and 
AOT-compiled, and there is
   no compile-time conditional that distinguishes the two. A newer TFM buys 
exactly one thing:
   `RuntimeFeature.IsDynamicCodeSupported` (netstandard2.1+/net5+) as a tidier 
probe than a
   `try`/`catch`. It is not required for a fix.
   
   ### Proposed fix, layer 1 - runtime probe (no API change, no new TFM)
   
   A once-computed flag, with the `GetCallingAssembly()` call left **inline in 
each public method**.
   It cannot be extracted into a helper: the calling assembly of such a helper 
is log4net itself, not
   the assembly that called log4net.
   
   ```csharp 
   internal static class CallerAssembly
   {
     internal static bool IsSupported { get; } = Probe();
   
     internal static Assembly Fallback { get; }
       = Assembly.GetEntryAssembly() ?? typeof(CallerAssembly).Assembly;
   
     private static bool Probe()
     { 
       try
       {
         return Assembly.GetCallingAssembly() is not null;
       }
       catch (PlatformNotSupportedException)
       {
         return false;
       }
     } 
   }    
   ``` 
   Call sites become:
   
   ```csharp
   public static ILog GetLogger(string name)
     => GetLogger(CallerAssembly.IsSupported ? Assembly.GetCallingAssembly() : 
CallerAssembly.Fallback, name);
   ```
   
   I validated the pattern with a two-assembly prototype published AOT: under 
JIT it returns the true
   calling assembly (the app, not the library); under AOT it returns the 
fallback with no exception.
   
   Cost: 18 mechanical edits, one static `bool`, no per-call overhead, works 
as-is on
   `net462`/`netstandard2.0`. Semantics degrade from "the caller's repository" 
to "the entry
   assembly's repository" - and since an AOT-published application is 
self-contained, that is almost
   always the same repository.
   
   ### Proposed fix, layer 2 - interceptor (accurate, opt-in, on top of layer 1)
   
   The needed public overloads already exist: `LogManager.GetLogger(Assembly, 
string)` and
   `GetLogger(Assembly, Type)`. A source generator can rewrite 
`LogManager.GetLogger(typeof(T))` at
   the call site to pass `typeof(ContainingTypeOfTheCallSite).Assembly`, which 
is *exactly* the calling
   assembly — strictly more accurate than layer 1's fallback, and it removes 
the reflection call
   entirely. Use Roslyn's `SemanticModel.GetInterceptableLocation`; do not 
hand-encode the v1
   location blob.
   
   Costs, which are why this should not be the only fix:
   
   - a new generator project shipped as `analyzers/dotnet/cs` in the package, 
plus a
     `build/log4net.props` that appends log4net's namespace to 
`<InterceptorsNamespaces>` so consumers
     do not have to set it themselves
   - interceptors are stable from the .NET 9.0.2xx SDK, so the *consumer* needs 
a recent SDK
     (log4net's own floor stays `net462`/`netstandard2.0`)
   - only calls in compilations that run the generator are intercepted - a 
pre-built third-party
     library calling `LogManager.GetLogger(name)` still needs layer 1
   - C# only; no help for VB/F# consumers
   
   ### Separate AOT blocker found while reproducing
   
   Fixing `GetCallingAssembly` does **not** make log4net AOT-clean. 
`SystemInfo`'s static constructor
   reads `ConfigurationManager.AppSettings`, which fails under AOT:
   
   ```
   System.Configuration.ConfigurationErrorsException: Configuration system 
failed to initialize
   ---> System.MissingMethodException: No parameterless constructor defined for 
type
         'System.Configuration.ClientConfigurationHost'.
      at System.Configuration.ConfigurationManager.get_AppSettings()
      at log4net.Util.SystemInfo.GetAppSetting(String)
      at log4net.Util.SystemInfo..cctor()
   ```
   
   If `System.Configuration.ConfigurationManager` is not in the closure at all, 
this surfaces as a
   fatal `TypeInitializationException` (`Could not find file
   'System.Configuration.ConfigurationManager'`) raised from 
`LoggerManager.OnProcessExit`, which
   aborts the process at shutdown. With the package present it degrades to a 
logged `log4net:ERROR`.
   
   Beyond that, `XmlConfigurator` instantiates appenders and layouts from type 
names via
   `Activator.CreateInstance`, which no amount of trim annotation can make 
safe. Real AOT
   configuration support means a code-first or source-generated configuration 
path - worth tracking
   separately so "fixes #233" is not read as "log4net is AOT-safe".


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to