Skip to content

Support of constructor parameters #188

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;

using Serilog.Configuration;
using System.Linq;

namespace Serilog.Settings.Configuration
{
Expand All @@ -26,11 +27,11 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
if (toType == typeof(IConfigurationSection)) return _section;

// process a nested configuration to populate an Action<> logger/sink config parameter?
var typeInfo = toType.GetTypeInfo();
if (typeInfo.IsGenericType &&
typeInfo.GetGenericTypeDefinition() is Type genericType && genericType == typeof(Action<>))
var toTypeInfo = toType.GetTypeInfo();
if (toTypeInfo.IsGenericType &&
toTypeInfo.GetGenericTypeDefinition() is Type genericType && genericType == typeof(Action<>))
{
var configType = typeInfo.GenericTypeArguments[0];
var configType = toTypeInfo.GenericTypeArguments[0];
if (configType != typeof(LoggerConfiguration) && configType != typeof(LoggerSinkConfiguration))
throw new ArgumentException($"Configuration for Action<{configType}> is not implemented.");

Expand All @@ -47,6 +48,72 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
}
}

if (toTypeInfo.IsInterface || toTypeInfo.IsAbstract)
{
if (!_section.GetChildren().Any())
{
// todo: add proper message
throw new InvalidOperationException();
}

var children = _section.GetChildren().ToList();

// todo: add proper checking
var parameterType = TypeHelper.FindType(children.FirstOrDefault(x => x.Key == "@type")?.Value);

if (parameterType == null)
{
throw new InvalidOperationException();
}

if (!toTypeInfo.IsAssignableFrom(parameterType))
{
//todo: parameterType isn't assignable to to type info
throw new InvalidOperationException();
}

// todo: get all parameters with values

var arguments = children.Where(x => x.Key != "@type").Select(x => new Tuple<string, IConfigurationSection>(x.Key, x)).ToList();
var argumentsNames = arguments.Select(x => x.Item1).ToList();

// todo: find a proper constructor:
// 1. all specified parameter can be configured by one constructor

var applicableConstructors = parameterType.GetTypeInfo().DeclaredConstructors
.Where(ci =>
{
var parameters = ci.GetParameters();
var requiredParametes = parameters.Where(p => !p.HasDefaultValue).ToList();

return argumentsNames.All(a => parameters.Any(p => p.Name == a)) && requiredParametes.All(p => argumentsNames.Contains(p.Name));
})
.OrderBy(ci => ci.GetParameters().Length)
.ToList();

if (!applicableConstructors.Any())
{
throw new InvalidOperationException();
}

var ctor = applicableConstructors.First();

var call = ctor.GetParameters()
.Select(pi =>
{
var configuredArgument = arguments.FirstOrDefault(a => a.Item1 == pi.Name);
if (configuredArgument == null) return pi.DefaultValue;

return configuredArgument.Item2.Get(pi.ParameterType);

})
.ToArray();

return ctor.Invoke(call);

//return _section.Get(parameterType);
}

// MS Config binding
return _section.Get(toType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)

// maybe it's the assembly-qualified type name of a concrete implementation
// with a default constructor
var type = FindType(argumentValue.Trim());
var type = TypeHelper.FindType(argumentValue.Trim());
if (type != null)
{
var ctor = type.GetTypeInfo().DeclaredConstructors.FirstOrDefault(ci =>
Expand All @@ -112,20 +112,6 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
return Convert.ChangeType(argumentValue, toType);
}

internal static Type FindType(string typeName)
{
var type = Type.GetType(typeName);
if (type == null)
{
if (!typeName.Contains(','))
{
type = Type.GetType($"{typeName}, Serilog");
}
}

return type;
}

internal static bool TryParseStaticMemberAccessor(string input, out string accessorTypeName, out string memberName)
{
if (input == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Linq;

namespace Serilog.Settings.Configuration
{
internal static class TypeHelper
{
internal static Type FindType(string typeName)
{
var type = Type.GetType(typeName);
if (type == null)
{
if (!typeName.Contains(','))
{
type = Type.GetType($"{typeName}, Serilog");
}
}

return type;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1107,5 +1107,33 @@ public void FilterWithIsAppliedWithCustomFilter()
log.ForContext("User", "the user").Write(Some.InformationEvent());
Assert.NotNull(evt);
}

[Fact]
public void SinkWithFormatterArgumentWithConstructorArguments()
{
var json = @"{
""Serilog"": {
""Using"": [""TestDummies""],
""WriteTo"": [{
""Name"": ""DummyWithFormatterSink"",
""Args"": {
""formatter"" : {
""@type"": ""Serilog.Formatting.Json.JsonFormatter"",
""renderMessage"": true
}
}
}]
}
}";

var log = ConfigFromJson(json)
.CreateLogger();

DummyWithFormatterSink.Reset();

log.Write(Some.InformationEvent());

Assert.Equal(1, DummyWithFormatterSink.Emitted.Count);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ public class DllScanningAssemblyFinderTests : IDisposable
const string BinDir2 = "bin2";
const string BinDir3 = "bin3";

#if PRIVATE_BIN
readonly string _privateBinPath;
#endif

public DllScanningAssemblyFinderTests()
{
#if PRIVATE_BIN
var d1 = GetOrCreateDirectory(BinDir1);
var d2 = GetOrCreateDirectory(BinDir2);
var d3 = GetOrCreateDirectory(BinDir3);

_privateBinPath = $"{d1.Name};{d2.FullName};{d3.Name}";
#endif

DirectoryInfo GetOrCreateDirectory(string name)
=> Directory.Exists(name) ? new DirectoryInfo(name) : Directory.CreateDirectory(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void TryParseStaticMemberAccessorReturnsExpectedResults(string input, str
[InlineData("Serilog.ConfigurationLoggerConfigurationExtensions", typeof(ConfigurationLoggerConfigurationExtensions))]
public void FindTypeSupportsSimpleNamesForSerilogTypes(string input, Type targetType)
{
var type = StringArgumentValue.FindType(input);
var type = TypeHelper.FindType(input);
Assert.Equal(targetType, type);
}

Expand Down
7 changes: 7 additions & 0 deletions test/TestDummies/DummyLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,12 @@ string hardCodedString
return loggerDestructuringConfiguration.With(new DummyHardCodedStringDestructuringPolicy(hardCodedString));
}

public static LoggerConfiguration DummyWithFormatterSink(
this LoggerSinkConfiguration loggerSinkConfiguration,
ITextFormatter formatter)
{
return loggerSinkConfiguration.Sink(new DummyWithFormatterSink(formatter));
}

}
}
34 changes: 34 additions & 0 deletions test/TestDummies/DummyWithFormatterSink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;

namespace TestDummies
{
public class DummyWithFormatterSink : ILogEventSink
{
[ThreadStatic]
static List<LogEvent> _emitted;

[ThreadStatic]
public static ITextFormatter Formatter;

public DummyWithFormatterSink(ITextFormatter formatter)
{
Formatter = formatter;
}

public static List<LogEvent> Emitted => _emitted ?? (_emitted = new List<LogEvent>());

public void Emit(LogEvent logEvent)
{
Emitted.Add(logEvent);
}

public static void Reset()
{
_emitted = null;
}
}
}