Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom NLog LayoutRenderer with constructor using Dependency Injection

I am trying to write a custom LayoutRenderer that logs data read from an object, but it seems that NLog is not working properly with Dependency Injection.

Here is my CustomLayoutRenderer:

[LayoutRenderer("custom-value")]
public class CustomLayoutRenderer : LayoutRenderer
{
    private readonly RequestContext _context;

    public CustomLayoutRenderer(RequestContext context)
    {
        _context = context;
    }

    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        builder.Append(_context.CustomValue);
    }
}

It is using this RequestContext object:

public class RequestContext
{
    public string CustomValue { get; set; } = "Valid custom value";
}

I am also wiring up DI, configuring NLog and registering my LayoutRenderer in Startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddScoped<RequestContext>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        LayoutRenderer.Register<CustomLayoutRenderer>("custom-value");

        loggerFactory.AddNLog();
        app.AddNLogWeb();
        env.ConfigureNLog("nlog.config");
        // ...
    }

I am then trying to use my ${custom-value} in nlog.config, but I am getting an error on the AddNLog() call:

2017-02-03 13:08:08.0284 Error Parsing configuration from [project-folder]\bin\Debug\net452\win7-x64\NLog.config failed. Exception: NLog.NLogConfigurationException: Exception when parsing [project-folder]\bin\Debug\net452\win7-x64\NLog.config. NLog.NLogConfigurationException: Cannot access the constructor of type: ATest.CustomLayoutRenderer. Is the required permission granted? at NLog.Internal.FactoryHelper.CreateInstance(Type t) ...

Notes

The reason why I am trying this is that I would like to log some information accessible only from the controller (like the TraceIdentifier, parts of the URL, and some request-specific custom stuff). The values in RequestContext would be set by the controller when it gets a request.

The following Renderer works as expected, which makes me think this is a dependency injection problem:

[LayoutRenderer("custom-value")]
public class CustomLayoutRenderer : LayoutRenderer
{
    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        builder.Append("Hello, World!");
    }
}

I did see this NLog bug but it's marked as fixed now, which is why I am posting here rather than there.

And for the sake of completeness, here is what I have added to my project.json:

"dependencies": {
    ...
    "NLog.Extensions.Logging": "1.0.0-*",
    "NLog.Web.AspNetCore": "4.3.0"
},
like image 551
KevinG Avatar asked Feb 03 '17 21:02

KevinG


1 Answers

Two methodes:

1) DI aware

You could make NLog DI aware. Add to your startup.cs:

ConfigurationItemFactory.Default.CreateInstance = (Type type) =>
{ 
    // your custom target. Could be a better check ;)
    if(type == typeof(CustomLayoutRenderer))
      return new CustomLayoutRenderer(...); // TODO get RequestContext
    else
      return Activator.CreateInstance(type); //default
};

This is a more generic approach.

2) AspNetMvcLayoutRendererBase

Or, override from AspNetMvcLayoutRendererBase (NLog.Web.AspNetCore) and use HttpContextAccessor?.HttpContext?.TryGetRequest() and don't add the constructor.

This only works when needing HttpContext.

[LayoutRenderer("custom-value")]
public class MyCustomRenderer : AspNetLayoutRendererBase
{
    protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
    {
        var httpRequest = HttpContextAccessor?.HttpContext?.TryGetRequest();

        if (httpRequest == null)
            return;


        builder.Append(httpRequest.Something); //TODO

    }
}
like image 93
Julian Avatar answered Sep 30 '22 12:09

Julian