Skip to content

Commit

Permalink
Make sure that single-file apps can find assemblies that contains sinks
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
0xced committed Mar 13, 2023
1 parent 679ce35 commit 488787a
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 44 deletions.
Expand Up @@ -5,6 +5,7 @@ namespace Serilog.Settings.Configuration.Assemblies;

abstract class AssemblyFinder
{
public abstract bool CanFindAssemblies { get; }
public abstract IReadOnlyList<AssemblyName> FindAssembliesContainingName(string nameToFind);

protected static bool IsCaseInsensitiveMatch(string text, string textToFind)
Expand All @@ -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)
Expand Down
@@ -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<AssemblyName> FindAssembliesContainingName(string nameToFind)
{
var assemblyNames = new List<AssemblyName>();
foreach (var assemblyFinder in _assemblyFinders)
{
assemblyNames.AddRange(assemblyFinder.FindAssembliesContainingName(nameToFind));
}
return assemblyNames;
}
}
Expand Up @@ -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<AssemblyName> FindAssembliesContainingName(string nameToFind)
{
if (_dependencyContext == null)
return Array.Empty<AssemblyName>();

var query = from library in _dependencyContext.RuntimeLibraries
where IsReferencingSerilog(library)
from assemblyName in library.GetDefaultAssemblyNames(_dependencyContext)
Expand Down
Expand Up @@ -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<AssemblyName> FindAssembliesContainingName(string nameToFind)
{
var probeDirs = new List<string>();

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)
Expand All @@ -58,4 +33,34 @@ static AssemblyName TryGetAssemblyNameFrom(string path)
}
}
}

static IEnumerable<string> 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);
}
}
}
Expand Up @@ -368,13 +368,23 @@ static IReadOnlyCollection<Assembly> 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"))
{
Expand Down

0 comments on commit 488787a

Please sign in to comment.