From 488787aabc5c5d9216a400022a31adc38d546f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Fri, 10 Mar 2023 21:26:08 +0100 Subject: [PATCH] Make sure that single-file apps can find assemblies that contains sinks Before this commit, when single-file app was detected, the behaviour was to fallback on DLL scanning. But DLL scanning would not find anything for an app published as a single-file, by sheer definition of single-file app! After this commit, an exception is thrown if the app is published as a single-file AND no `Serilog:Using` section is defined in the configuration. The error message explains that either a `Serilog:Using` section must be added or show how to explicitly configure assemblies through the `ConfigurationReaderOptions`. --- .../Assemblies/AssemblyFinder.cs | 16 +---- .../Assemblies/AutoAssemblyFinder.cs | 25 ++++++++ .../DependencyContextAssemblyFinder.cs | 7 ++- .../Assemblies/DllScanningAssemblyFinder.cs | 61 ++++++++++--------- .../Configuration/ConfigurationReader.cs | 12 +++- 5 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AutoAssemblyFinder.cs diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs index c2c3481..6fd9e10 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs @@ -5,6 +5,7 @@ namespace Serilog.Settings.Configuration.Assemblies; abstract class AssemblyFinder { + public abstract bool CanFindAssemblies { get; } public abstract IReadOnlyList FindAssembliesContainingName(string nameToFind); protected static bool IsCaseInsensitiveMatch(string text, string textToFind) @@ -14,20 +15,7 @@ protected static bool IsCaseInsensitiveMatch(string text, string textToFind) public static AssemblyFinder Auto() { - try - { - // Need to check `Assembly.GetEntryAssembly()` first because - // `DependencyContext.Default` throws an exception when `Assembly.GetEntryAssembly()` returns null - if (Assembly.GetEntryAssembly() != null && DependencyContext.Default != null) - { - return new DependencyContextAssemblyFinder(DependencyContext.Default); - } - } - catch (NotSupportedException) when (typeof(object).Assembly.Location is "") // bundled mode detection - { - } - - return new DllScanningAssemblyFinder(); + return new AutoAssemblyFinder(new DependencyContextAssemblyFinder(DependencyContext.Default), new DllScanningAssemblyFinder()); } public static AssemblyFinder ForSource(ConfigurationAssemblySource configurationAssemblySource) diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AutoAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AutoAssemblyFinder.cs new file mode 100644 index 0000000..f0092fd --- /dev/null +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AutoAssemblyFinder.cs @@ -0,0 +1,25 @@ +using System.Reflection; + +namespace Serilog.Settings.Configuration.Assemblies; + +class AutoAssemblyFinder : AssemblyFinder +{ + readonly AssemblyFinder[] _assemblyFinders; + + public AutoAssemblyFinder(params AssemblyFinder[] assemblyFinders) + { + _assemblyFinders = assemblyFinders; + } + + public override bool CanFindAssemblies => _assemblyFinders.Any(e => e.CanFindAssemblies); + + public override IReadOnlyList FindAssembliesContainingName(string nameToFind) + { + var assemblyNames = new List(); + foreach (var assemblyFinder in _assemblyFinders) + { + assemblyNames.AddRange(assemblyFinder.FindAssembliesContainingName(nameToFind)); + } + return assemblyNames; + } +} diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs index 13e24f2..8fec93c 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs @@ -9,11 +9,16 @@ sealed class DependencyContextAssemblyFinder : AssemblyFinder public DependencyContextAssemblyFinder(DependencyContext dependencyContext) { - _dependencyContext = dependencyContext ?? throw new ArgumentNullException(nameof(dependencyContext)); + _dependencyContext = dependencyContext; } + public override bool CanFindAssemblies => _dependencyContext != null; + public override IReadOnlyList FindAssembliesContainingName(string nameToFind) { + if (_dependencyContext == null) + return Array.Empty(); + var query = from library in _dependencyContext.RuntimeLibraries where IsReferencingSerilog(library) from assemblyName in library.GetDefaultAssemblyNames(_dependencyContext) diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs index 4d7570c..2fa2865 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs @@ -4,36 +4,11 @@ namespace Serilog.Settings.Configuration.Assemblies; sealed class DllScanningAssemblyFinder : AssemblyFinder { + public override bool CanFindAssemblies => GetProbeDirs().SelectMany(e => Directory.GetFiles(e, "*.dll")).Any(); + public override IReadOnlyList FindAssembliesContainingName(string nameToFind) { - var probeDirs = new List(); - - if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory)) - { - probeDirs.Add(AppDomain.CurrentDomain.BaseDirectory); - -#if NETFRAMEWORK - var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath; - if (!string.IsNullOrEmpty(privateBinPath)) - { - foreach (var path in privateBinPath.Split(';')) - { - if (Path.IsPathRooted(path)) - { - probeDirs.Add(path); - } - else - { - probeDirs.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path)); - } - } - } -#endif - } - else - { - probeDirs.Add(Path.GetDirectoryName(typeof(AssemblyFinder).Assembly.Location)); - } + var probeDirs = GetProbeDirs(); var query = from probeDir in probeDirs where Directory.Exists(probeDir) @@ -58,4 +33,34 @@ static AssemblyName TryGetAssemblyNameFrom(string path) } } } + + static IEnumerable GetProbeDirs() + { + if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory)) + { + yield return AppDomain.CurrentDomain.BaseDirectory; + +#if NETFRAMEWORK + var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath; + if (!string.IsNullOrEmpty(privateBinPath)) + { + foreach (var path in privateBinPath.Split(';')) + { + if (Path.IsPathRooted(path)) + { + yield return path; + } + else + { + yield return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path); + } + } + } +#endif + } + else + { + yield return Path.GetDirectoryName(typeof(AssemblyFinder).Assembly.Location); + } + } } diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs index f0202f2..1047b54 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs @@ -368,13 +368,23 @@ static IReadOnlyCollection LoadConfigurationAssemblies(IConfigurationS { if (string.IsNullOrWhiteSpace(simpleName)) throw new InvalidOperationException( - "A zero-length or whitespace assembly name was supplied to a Serilog.Using configuration statement."); + $"A zero-length or whitespace assembly name was supplied to a {usingSection.Path} configuration statement."); var assembly = Assembly.Load(new AssemblyName(simpleName)); if (!assemblies.ContainsKey(assembly.FullName)) assemblies.Add(assembly.FullName, assembly); } } + else if (!assemblyFinder.CanFindAssemblies) + { + var message = $""" + The application is published as single-file and no {usingSection.Path} configuration section is defined. + Either add a {usingSection.Path} section or explicitly specify assemblies that contains sinks and other types through the reader options. For example: + var options = new ConfigurationReaderOptions(typeof(ConsoleLoggerConfigurationExtensions).Assembly, typeof(SerilogExpression).Assembly); + new LoggerConfiguration().ReadFrom.Configuration(configuration, options); + """; + throw new InvalidOperationException(message); + } foreach (var assemblyName in assemblyFinder.FindAssembliesContainingName("serilog")) {