serilog-settings-configuration
serilog-settings-configuration copied to clipboard
ConfigurationReader is attempting to load assemblies not associated with the project but found by the DllScanningAssemblyLoader
I've found following bug:
The DllScanningAssemlyFinder is scanning the Application's directory for dlls containing .serilog in its name. Later on the ConfigurationReader attempts to load the results with the Assembly.Load function.
Unfortunately Assembly.Load only works with assemblies referenced by the application and does no actual disk lookup. So a user attempted to add a new sink to an application, copying manually the sink's dll into the folder will have no success, as the Dll scanner will find the dll but the ConfigurationReader is unable to load it and the application will crash with a FileNotFoundException.
The behavior of Assembly.Load has changed since in the .NET5 framework: https://github.com/dotnet/runtime/issues/62522
See my other ticket here, how this error will sho up: https://github.com/serilog/serilog-settings-configuration/issues/407
Thanks for the note. Dealing with LoadFrom() isn't always straightforward, so I'm not sure we'll want to attempt it directly in this project.
Your best bet is to use the ConfigurationReaderOptions(Assembly[]) constructor, and pass through a list of assemblies you've found and loaded via LoadFrom() in the application code. This could be as direct as running over all Serilog.Sinks.*.dll files in your deployment directory and loading them up, depending on your environment. HTH!
As a work around I've just added references to all sinks we are using to all projects. But I think it clearly should be considered to not do a file based lookup of dlls, as there is the potential, that they can't be loaded. The DllScanningAssemblyFinder will only work on the classic .NET framework and will break newer applications, if it finds a file not referenced by the project.
As a work around I've just added references to all sinks we are using to all projects.
You should probably do what @nblumhardt suggested instead, i.e. passing explicit assemblies to the ConfigurationReaderOptions. This is safer and will work for single-file deployments.
var configurationAssemblies = new[]
{
typeof(ILogger).Assembly, // Serilog
typeof(SerilogExpression).Assembly, // Serilog.Expressions
typeof(ProcessLoggerConfigurationExtensions).Assembly, // Serilog.Enrichers.Process
typeof(ThreadLoggerConfigurationExtensions).Assembly, // Serilog.Enrichers.Thread
typeof(Log4NetTextFormatter).Assembly, // Serilog.Formatting.Log4Net
typeof(ConsoleLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.Console
typeof(LoggerConfigurationEventLogExtensions).Assembly, // Serilog.Sinks.EventLog
typeof(FileLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.File
typeof(NotepadLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.Notepad
typeof(UdpClientFactory).Assembly, // Serilog.Sinks.Udp
};
var readerOptions = new ConfigurationReaderOptions(configurationAssemblies);
configuration.ReadFrom.Configuration(context.Configuration, readerOptions);
But I think it clearly should be considered to not do a file based lookup of dlls, as there is the potential, that they can't be loaded.
This is exactly what happens when you explicitly pass assemblies as in the example above: no file based lookup occurs at all.
Of course I can do that, but isn't that simply a work around? As I see it, currently the ConfigurationReader is able to execute a code path which might lead to a crash due to an unsupported combination of file based lookup of dlls and the later Assembly.Load. Further more, somebody unaware of the fact that the assemblies have been defined manually, could have a hard time adding a new sink because he first must find this declaration and extend it. No blocker but imho not intuitiv too.
As a work around I've just added references to all sinks we are using to all projects.
You should probably do what @nblumhardt suggested instead, i.e. passing explicit assemblies to the
ConfigurationReaderOptions. This is safer and will work for single-file deployments.var configurationAssemblies = new[] { typeof(ILogger).Assembly, // Serilog typeof(SerilogExpression).Assembly, // Serilog.Expressions typeof(ProcessLoggerConfigurationExtensions).Assembly, // Serilog.Enrichers.Process typeof(ThreadLoggerConfigurationExtensions).Assembly, // Serilog.Enrichers.Thread typeof(Log4NetTextFormatter).Assembly, // Serilog.Formatting.Log4Net typeof(ConsoleLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.Console typeof(LoggerConfigurationEventLogExtensions).Assembly, // Serilog.Sinks.EventLog typeof(FileLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.File typeof(NotepadLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.Notepad typeof(UdpClientFactory).Assembly, // Serilog.Sinks.Udp }; var readerOptions = new ConfigurationReaderOptions(configurationAssemblies); configuration.ReadFrom.Configuration(context.Configuration, readerOptions);But I think it clearly should be considered to not do a file based lookup of dlls, as there is the potential, that they can't be loaded.
This is exactly what happens when you explicitly pass assemblies as in the example above: no file based lookup occurs at all.
Does specifying a list of assemblies disable the dynamic lookup of dll's?
I'm running into this issue:
Hosting net8 app on IIS.
I remove a reference to Serilog.UI and run msdeploy to publish the app.
msdeploy updates all files, but does not perform a file clean beforehand (default settings).
3 dlls then are left behind in:
Then msdeploy fails to start the app:
Reproducing this locally (just pasting the Serilog.UI files in the output) gives me a FileNotFoundException:
In my case it took a long time to figure out why it was loading Serilog.UI after i removed the reference since it was not used in a Usings block in any configs, and the error message was vague. After inspecting the code online i found that it tries to dynamically load any serilog dll in addition to looking at the usings.
Also i'm now puzzled why it fails to load the DLL, because it should load just fine, its in the output directory it was the right version etc.
Anyhow in my case the 'fix' should be to just remove the extra dll's - but this seems like it could happen again in the future so i thought i'd bring it up.
Also i'm now puzzled why it fails to load the DLL, because it should load just fine, its in the output directory it was the right version etc.
@sommmen: Because Assembly.Load doesn't load files from the disk which are not referenced by the project in anyway, for security reasons. And that's the issue with serilogy, because it uses a file based lookup and attempting to load the found files, if referenced or not.
I think I stumbled on a closely related problem. In a .net 4.8 application, I get an unhandled exception on startup because it loads (or attempts to load) some DLL. But the problem is, it fails one one specific system and works just fine elsewhere. As you might guess it's the customer's system.
I don't have regular access to the system, but I got this from the ETW application log:
Description: The process was terminated due to an unhandled exception.
Exception Info: System.NotSupportedException
Exception Info: System.IO.FileLoadException
at System.Reflection.RuntimeAssembly._nLoad(System.Reflection.AssemblyName, System.String, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
at System.Reflection.Assembly.Load(System.Reflection.AssemblyName)
at Serilog.Settings.Configuration.ConfigurationReader.LoadConfigurationAssemblies(Microsoft.Extensions.Configuration.IConfiguration, Serilog.Settings.Configuration.Assemblies.AssemblyFinder)
So it seems that on this one system, the logic fails even before .net 5. My wild guess is some 32 Bit 3rd-party DLL in the GAC or somewhere found by the Auto resolver. I will try the workarounds suggested here next me I get the chance.
It would be nice if this behaviour could be disabled in the config file though.
i experienced the same behavior after copying the application to another machine because windows flagged the DLLs as "downloaded". after unblocking them, the application started again.
https://www.tenforums.com/tutorials/5357-unblock-file-windows-10-a.html
I've also run into this, though I can't quite fathom why.
I have two copies of a Visual Studio 2022 solution from different GIT branches - one of these makes use of Azure Storage and is configured with a Serilog Azure Blob Storage sink - this works fine. The other will not start with the following error:
System.IO.FileNotFoundException: 'Could not load file or assembly 'Serilog.Sinks.AzureBlobStorage, Version=4.0.5.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.'
I've spent quite a bit of time trying to figure out what was going on - originally I thought either I'd accidentally added some configuration to the wrong solution or it was loading configuration from a cache somewhere. Trying to eliminate that I've made sure that both solutions are using different IIS ports, projects and solutions have different GUIDs... None of that helped.
I discovered that assemblyFinder.FindAssembliesContainingName("Serilog") in ConfigurationReader.LoadConfigurationAssemblies() was returning "Serilog.Sinks.AzureBlobStorage" even though it isn't present anywhere in the current solution (as a DLL, reference, configuration - it's not used at all).
I've tried clearing my NuGet cache to see if it was being picked up from there - no change.
The solution without Blob Storage support was working until very recently when I set up the parallel branched version.
...and a few minutes after deciding I'd got to the end of the line and posting the above, I discovered that there were a bunch of DLLs kicking around the /bin folders of the solution from when it had been on the other GIT branch. Including Serilog.Sinks.AzureBlobStorage. They weren't loaded, but FindAssembliesContainingName was finding them.
Despite cleaning the solution the DLLs were still left - I had to manually delete the /bin and /obj folders. Not just a Serilog issue, either - I got past the Serilog.Sinks.AzureBlobStorage issue by adding the NuGet package (not really acceptable, as I can't just add a load of random dependencies to a production system, but in the interests of getting a working dev environment...) that fixed that issue, but the site then started throwing similar errors for other DLLs that are present only on the newer branch, including a class library that's part of the solution and definitely not going to be kicking around any global caches. So, this was untidy (and not branch switching friendly) behaviour by Visual Studio, not really a Serilog issue at all.
https://github.com/serilog/serilog-settings-configuration/issues/406#issuecomment-1878440317