Skip to content

Commit 488787a

Browse files
committed
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`.
1 parent 679ce35 commit 488787a

File tree

5 files changed

+77
-44
lines changed

5 files changed

+77
-44
lines changed

src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Serilog.Settings.Configuration.Assemblies;
55

66
abstract class AssemblyFinder
77
{
8+
public abstract bool CanFindAssemblies { get; }
89
public abstract IReadOnlyList<AssemblyName> FindAssembliesContainingName(string nameToFind);
910

1011
protected static bool IsCaseInsensitiveMatch(string text, string textToFind)
@@ -14,20 +15,7 @@ protected static bool IsCaseInsensitiveMatch(string text, string textToFind)
1415

1516
public static AssemblyFinder Auto()
1617
{
17-
try
18-
{
19-
// Need to check `Assembly.GetEntryAssembly()` first because
20-
// `DependencyContext.Default` throws an exception when `Assembly.GetEntryAssembly()` returns null
21-
if (Assembly.GetEntryAssembly() != null && DependencyContext.Default != null)
22-
{
23-
return new DependencyContextAssemblyFinder(DependencyContext.Default);
24-
}
25-
}
26-
catch (NotSupportedException) when (typeof(object).Assembly.Location is "") // bundled mode detection
27-
{
28-
}
29-
30-
return new DllScanningAssemblyFinder();
18+
return new AutoAssemblyFinder(new DependencyContextAssemblyFinder(DependencyContext.Default), new DllScanningAssemblyFinder());
3119
}
3220

3321
public static AssemblyFinder ForSource(ConfigurationAssemblySource configurationAssemblySource)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Reflection;
2+
3+
namespace Serilog.Settings.Configuration.Assemblies;
4+
5+
class AutoAssemblyFinder : AssemblyFinder
6+
{
7+
readonly AssemblyFinder[] _assemblyFinders;
8+
9+
public AutoAssemblyFinder(params AssemblyFinder[] assemblyFinders)
10+
{
11+
_assemblyFinders = assemblyFinders;
12+
}
13+
14+
public override bool CanFindAssemblies => _assemblyFinders.Any(e => e.CanFindAssemblies);
15+
16+
public override IReadOnlyList<AssemblyName> FindAssembliesContainingName(string nameToFind)
17+
{
18+
var assemblyNames = new List<AssemblyName>();
19+
foreach (var assemblyFinder in _assemblyFinders)
20+
{
21+
assemblyNames.AddRange(assemblyFinder.FindAssembliesContainingName(nameToFind));
22+
}
23+
return assemblyNames;
24+
}
25+
}

src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ sealed class DependencyContextAssemblyFinder : AssemblyFinder
99

1010
public DependencyContextAssemblyFinder(DependencyContext dependencyContext)
1111
{
12-
_dependencyContext = dependencyContext ?? throw new ArgumentNullException(nameof(dependencyContext));
12+
_dependencyContext = dependencyContext;
1313
}
1414

15+
public override bool CanFindAssemblies => _dependencyContext != null;
16+
1517
public override IReadOnlyList<AssemblyName> FindAssembliesContainingName(string nameToFind)
1618
{
19+
if (_dependencyContext == null)
20+
return Array.Empty<AssemblyName>();
21+
1722
var query = from library in _dependencyContext.RuntimeLibraries
1823
where IsReferencingSerilog(library)
1924
from assemblyName in library.GetDefaultAssemblyNames(_dependencyContext)

src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,11 @@ namespace Serilog.Settings.Configuration.Assemblies;
44

55
sealed class DllScanningAssemblyFinder : AssemblyFinder
66
{
7+
public override bool CanFindAssemblies => GetProbeDirs().SelectMany(e => Directory.GetFiles(e, "*.dll")).Any();
8+
79
public override IReadOnlyList<AssemblyName> FindAssembliesContainingName(string nameToFind)
810
{
9-
var probeDirs = new List<string>();
10-
11-
if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory))
12-
{
13-
probeDirs.Add(AppDomain.CurrentDomain.BaseDirectory);
14-
15-
#if NETFRAMEWORK
16-
var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
17-
if (!string.IsNullOrEmpty(privateBinPath))
18-
{
19-
foreach (var path in privateBinPath.Split(';'))
20-
{
21-
if (Path.IsPathRooted(path))
22-
{
23-
probeDirs.Add(path);
24-
}
25-
else
26-
{
27-
probeDirs.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path));
28-
}
29-
}
30-
}
31-
#endif
32-
}
33-
else
34-
{
35-
probeDirs.Add(Path.GetDirectoryName(typeof(AssemblyFinder).Assembly.Location));
36-
}
11+
var probeDirs = GetProbeDirs();
3712

3813
var query = from probeDir in probeDirs
3914
where Directory.Exists(probeDir)
@@ -58,4 +33,34 @@ static AssemblyName TryGetAssemblyNameFrom(string path)
5833
}
5934
}
6035
}
36+
37+
static IEnumerable<string> GetProbeDirs()
38+
{
39+
if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory))
40+
{
41+
yield return AppDomain.CurrentDomain.BaseDirectory;
42+
43+
#if NETFRAMEWORK
44+
var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
45+
if (!string.IsNullOrEmpty(privateBinPath))
46+
{
47+
foreach (var path in privateBinPath.Split(';'))
48+
{
49+
if (Path.IsPathRooted(path))
50+
{
51+
yield return path;
52+
}
53+
else
54+
{
55+
yield return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path);
56+
}
57+
}
58+
}
59+
#endif
60+
}
61+
else
62+
{
63+
yield return Path.GetDirectoryName(typeof(AssemblyFinder).Assembly.Location);
64+
}
65+
}
6166
}

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,13 +368,23 @@ static IReadOnlyCollection<Assembly> LoadConfigurationAssemblies(IConfigurationS
368368
{
369369
if (string.IsNullOrWhiteSpace(simpleName))
370370
throw new InvalidOperationException(
371-
"A zero-length or whitespace assembly name was supplied to a Serilog.Using configuration statement.");
371+
$"A zero-length or whitespace assembly name was supplied to a {usingSection.Path} configuration statement.");
372372

373373
var assembly = Assembly.Load(new AssemblyName(simpleName));
374374
if (!assemblies.ContainsKey(assembly.FullName))
375375
assemblies.Add(assembly.FullName, assembly);
376376
}
377377
}
378+
else if (!assemblyFinder.CanFindAssemblies)
379+
{
380+
var message = $"""
381+
The application is published as single-file and no {usingSection.Path} configuration section is defined.
382+
Either add a {usingSection.Path} section or explicitly specify assemblies that contains sinks and other types through the reader options. For example:
383+
var options = new ConfigurationReaderOptions(typeof(ConsoleLoggerConfigurationExtensions).Assembly, typeof(SerilogExpression).Assembly);
384+
new LoggerConfiguration().ReadFrom.Configuration(configuration, options);
385+
""";
386+
throw new InvalidOperationException(message);
387+
}
378388

379389
foreach (var assemblyName in assemblyFinder.FindAssembliesContainingName("serilog"))
380390
{

0 commit comments

Comments
 (0)