Skip to content

Add structured logging support#142

Open
andreycha wants to merge 2 commits into
huorswords:developfrom
andreycha:feat/add-structured-logging-support
Open

Add structured logging support#142
andreycha wants to merge 2 commits into
huorswords:developfrom
andreycha:feat/add-structured-logging-support

Conversation

@andreycha
Copy link
Copy Markdown

@andreycha andreycha commented Apr 12, 2024

Motivation
Structured logging is a standard practice nowadays. When using ILogger and co .NET analyzers even encourage to use it. To comply with the best practice and other logger implementations, this PR adds support for structured logging.

Changes

  • Add message template string and its arguments to logging event properties
  • Expose a method to enrich properties
  • Update documentation

Things to discuss

  • Message template string is added as a property named "MessageTemplate". Motivation for that is to match Serilog implementation. This should make life a bit easier in case people switch between Serilog and log4net. This also makes life easier in case of a service landscape with mixed logger implementations whose logs are then streamed into a central location with unified schema (e.g., Elastic and ElasticCommonSchema).
  • Argument values are currently added as strings to match the same behavior as for scope values. Although I'd strongly advocate for adding at least atomic values as is. Agains, that's how Serilog implementation also behaves.
  • LoggingEventFactory mock was replaced with actual custom implementations in few tests. Main reason is that Moq doesn't allow to mock protected generic method. It also makes test setup easier to understand.
  • I think it would be beneficial (also for us) to have this changes available as early as version 6.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds structured logging support to the log4net provider by capturing message templates and template arguments as LoggingEvent properties, and exposes an additional enrichment hook for customization.

Changes:

  • Add {OriginalFormat}/message template and template arguments from ILogger state into LoggingEvent.Properties (including mapping to MessageTemplate).
  • Introduce EnrichProperties(...) as an overridable extension point (and adjust enrichment ordering vs scopes).
  • Update tests and documentation to reflect new customization approach.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/Unit.Tests/Mocks/OverriddenScopesLoggingEventFactory.cs Test helper factory overriding EnrichWithScopes for custom property injection.
tests/Unit.Tests/Mocks/OverriddenPropertiesLoggingEventFactory.cs Test helper factory overriding EnrichProperties for custom property injection.
tests/Unit.Tests/Log4NetLoggerTests.cs Replace Moq.Protected approach with concrete test factories; add tests for overridden enrichment paths.
src/Microsoft.Extensions.Logging.Log4Net.AspNetCore/Log4NetLoggingEventFactory.cs Add structured logging property enrichment (MessageTemplate + args) and new EnrichProperties hook.
doc/ModifyingLoggingBehaviour.md Document customization via implementing the factory interface and by inheriting the default factory.
README.md Document structured logging behavior at a high level.
Comments suppressed due to low confidence (1)

doc/ModifyingLoggingBehaviour.md:96

  • The documentation sample now declares CustomLoggingEventFactory : ILog4NetLoggingEventFactory, but the shown CreateLoggingEvent method has no access modifier (so it would be private) and the signature shown below no longer matches the actual ILog4NetLoggingEventFactory.CreateLoggingEvent signature (notably the in parameter and IExternalScopeProvider scopeProvider). Please update the snippet so it would compile against the current interface.
public class CustomLoggingEventFactory : ILog4NetLoggingEventFactory
{
    LoggingEvent CreateLoggingEvent<TState>(
        MessageCandidate<TState> messageCandidate,
        ILogger logger,
        Log4NetProviderOptions options
    ) 
    {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Microsoft.Extensions.Logging.Log4Net.AspNetCore/Log4NetLoggingEventFactory.cs Outdated
Comment on lines +178 to +196
protected virtual void EnrichProperties<TState>(LoggingEvent loggingEvent, in MessageCandidate<TState> messageCandidate)
{
loggingEvent.Properties[EventIdProperty] = messageCandidate.EventId;

// State is always passed as Microsoft.Extensions.Logging.FormattedLogValues which is internal
// but implements IReadOnlyCollection<KeyValuePair<string, object>>
if (messageCandidate.State is IReadOnlyCollection<KeyValuePair<string, object>> stateProperties)
{
foreach (var kvp in stateProperties)
{
var key = kvp.Key;
if (kvp.Key == OriginalFormatProperty && kvp.Value is string)
{
// change property name to match Serilog
key = "MessageTemplate";
}
loggingEvent.Properties[key] = ConvertValue(kvp.Value);
}
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment on lines +153 to +163
It is also possible to inherit default implementation `LoggingEventFactory` and override `EnrichWithScope()` and/or `EnrichProperties` methods:

```csharp
public class CustomLoggingEventFactory : LoggingEventFactory
{
protected override void EnrichProperties<TState>(LoggingEvent loggingEvent, in MessageCandidate<TState> messageCandidate)
{
base.EnrichProperties(loggingEvent, in messageCandidate);
loggingEvent.Properties["ApplicationName"] = Assembly.GetEntryAssembly().GetName().Name;
loggingEvent.Properties["ApplicationVersion"] = Assembly.GetEntryAssembly().GetName().Version.ToString();
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment on lines 299 to 300
LoggerExternalScopeProvider scopeProvider = new LoggerExternalScopeProvider();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

}

[Fact]
public void Log_Should_Emit_Custom_Scope_Operations_Created_By_Overriden_Enrich_Properties_Method()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment on lines +5 to +7
#if NET6_0
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Entities;
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Scope;
#endif
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@huorswords
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants