2

So far we've been using log4net in projects targeting .NET 4.6.1, which works like a charm. Some months ago we started implementing our first .NET core projects. But log4net seems to have it's problems with this framework. We have written our own appender pushing all log events to our logging system. However in core projects essential information is missing in the LoggingEvent objects.

I've set up a minimal test project called Log4NetUnitTest to repeat that behaviour.

The csproj file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="log4net" Version="2.0.8" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
    <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
    <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
  </ItemGroup>

</Project>

The unit test class configures the logger programmatically (I've also tested configuration by file with the same result):

[TestClass]
public class LoggingTests
{
    [TestMethod]
    public void Log()
    {
        var logger = LogManager.GetLogger(typeof(LoggingTests));
        var appender = new MyAppender();

        var loggerLogger = (log4net.Repository.Hierarchy.Logger)logger.Logger;
        loggerLogger.Level = Level.All;
        loggerLogger.AddAppender(appender);
        loggerLogger.Hierarchy.Configured = true;

        try
        {
            throw new Exception("Some exception");
        }
        catch (Exception e)
        {
            logger.Error("Some error text", e);
        }
    }
}

The custom appender (which simply writes the JSON formatted event to the console):

public sealed class MyAppender : AppenderSkeleton
{
    private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings
    {
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
        Formatting = Formatting.Indented
    };

    protected override void Append(LoggingEvent loggingEvent)
    {
        var loggingObject = new
        {
            ApplicationName = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name ?? System.Reflection.Assembly.GetExecutingAssembly().GetName().Name,
            Method = loggingEvent.LocationInformation.MethodName,
            Line = loggingEvent.LocationInformation.LineNumber,
            Class = loggingEvent.LocationInformation.ClassName,
            loggingEvent.LoggerName,
            Environment.MachineName,
            ExceptionMessage = loggingEvent.ExceptionObject?.Message,
            Message = loggingEvent.MessageObject,
            Level = loggingEvent.Level.Name
        };

        string serializedLog = JsonConvert.SerializeObject(loggingObject, _settings);

        Console.WriteLine(serializedLog);
    }
}

When you run the test, you will get the following output:

{
  "ApplicationName": "testhost",
  "Method": "?",
  "Line": "?",
  "Class": "?",
  "LoggerName": "Log4NetUnitTest.LoggingTests",
  "MachineName": "NBG-ITSD-138",
  "ExceptionMessage": "Some exception",
  "Message": "Some error text",
  "Level": "ERROR"
}

So Method, Line and Class cannot be resolved. Also some other properties (which we are not using) like Domain, Identity and UserName are just set to "NOT AVAILABLE". When you change TargetFramework to net461 all properties are set correctly:

{
  "ApplicationName": "Log4NetUnitTest",
  "Method": "Log",
  "Line": "31",
  "Class": "Log4NetUnitTest.LoggingTests",
  "LoggerName": "Log4NetUnitTest.LoggingTests",
  "MachineName": "NBG-ITSD-138",
  "ExceptionMessage": "Some exception",
  "Message": "Some error text",
  "Level": "ERROR"
}

Some years ago someone had a similar problem (not NET core), which could be solved adding loggingEvent.Fix = FixFlags.All; at the beginning of the Append() method. Didn't work for me though:-(

I guess there's some configuration or an additional package I need to add, but I have no clue what's wrong. Any suggestions are greatly appreciated.

Ash
  • 3,283
  • 6
  • 16
  • 20
  • Where you able to resolve this issue? – JP Garza Oct 29 '19 at 18:21
  • 1
    @JPGarza Well, not with the log4net package alone. Within our appender, I check if fields like `MethodName` or `LineNumber` are set correctly. If not, I parse the `StackTrace` myself to extract the location infos. In essence, it's the code of the log4net `LocationInfo` class itself with a few adjustments. – Ash Oct 31 '19 at 07:19

1 Answers1

1

Looking at the source for LocationInfo.cs it seems that a lot of functionality is wrapped in a #if !(NETCF || NETSTANDARD1_3) block, which is why none of it is set when using .NET Core. I presume this functionality was not available in .NET Standard 1.3.

There is an issue LOG4NET-606 in the Apache JIRA which mentions that later versions of .NET Standard added the missing functionality, and these values could be restored if log4Net was upgraded to target a higher .NET Standard version. Voting and commenting on this issue may help things along.

TimS
  • 2,085
  • 20
  • 31