Skip to content
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

Add MinimumEventLevel to Sentry.Log4Net and convert events below it to breadcrumbs #2505

Merged
merged 17 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from 15 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

- Added Sampling Decision to Trace Envelope Header ([#2495](https://github.com/getsentry/sentry-dotnet/pull/2495))
- Add MinimumEventLevel to Sentry.Log4Net and convert events below it to breadcrumbs ([#2505](https://github.com/getsentry/sentry-dotnet/pull/2505))

### Fixes

Expand Down
4 changes: 4 additions & 0 deletions samples/Sentry.Samples.Log4Net/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ private static void Main()
// Does not result in an event in Sentry
Log.Debug("Debug message which is not sent.");

// app.config sets MinimumEventLevel to ERROR, so every below that level is added as a breadcrumb.
Log.Info("Info message which is added as a breadcrumb.");
Log.Error("Error message which is sent as an event.");

try
{
DoWork();
Expand Down
3 changes: 3 additions & 0 deletions samples/Sentry.Samples.Log4Net/app.config
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<!--Sends the log event Identity value as the user-->
<SendIdentity value="true" />
<Environment value="dev" />
<!--If MinimumEventLevel is specified, every event above threshold
and below MinimumEventLevel will be added as a breadcrumb.-->
<MinimumEventLevel value="ERROR" />
<threshold value="INFO" />
</appender>
<root>
Expand Down
14 changes: 14 additions & 0 deletions src/Sentry.Log4Net/LevelMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,18 @@ internal static class LevelMapping
_ => null
};
}

public static BreadcrumbLevel? ToBreadcrumbLevel(this LoggingEvent loggingLevel)
{
return loggingLevel.Level switch
{
var l when l == Level.Fatal || l == Level.Emergency => BreadcrumbLevel.Critical,
var l when l == Level.Alert || l == Level.Critical || l == Level.Severe || l == Level.Error => BreadcrumbLevel.Error,
var l when l == Level.Warn => BreadcrumbLevel.Warning,
var l when l == Level.Notice || l == Level.Info => BreadcrumbLevel.Info,
var l when l == Level.Debug || l == Level.Verbose || l == Level.Trace || l == Level.Finer || l == Level.Finest ||
l == Level.Fine || l == Level.All => BreadcrumbLevel.Debug,
_ => null
};
}
jamescrosswell marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 20 additions & 0 deletions src/Sentry.Log4Net/SentryAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ internal static readonly SdkVersion NameAndVersion
/// </summary>
public string? Environment { get; set; }

/// <summary>
/// Lowest level required for a log message to become an event.
/// Every level above threshold and below this level will become a breadcrumb.
/// </summary>
public Level? MinimumEventLevel { get; set; }

/// <summary>
/// log4net SDK name.
/// </summary>
Expand Down Expand Up @@ -79,6 +85,20 @@ protected override void Append(LoggingEvent loggingEvent)
}

var exception = loggingEvent.ExceptionObject ?? loggingEvent.MessageObject as Exception;

if (MinimumEventLevel is not null && loggingEvent.Level < MinimumEventLevel)
{
var message = !string.IsNullOrWhiteSpace(loggingEvent.RenderedMessage) ? loggingEvent.RenderedMessage : string.Empty;
var category = loggingEvent.LoggerName;
var level = loggingEvent.ToBreadcrumbLevel();
IDictionary<string, string> data = GetLoggingEventProperties(loggingEvent)
.Where(kvp => kvp.Value != null)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value!.ToString());

_hub.AddBreadcrumb(message, category, type: null, data, level ?? default);
return;
}

var evt = new SentryEvent(exception)
{
Logger = loggingEvent.LoggerName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
[assembly: System.CLSCompliant(true)]
[assembly: System.CLSCompliant(true)]
namespace Sentry.Log4Net
{
public class SentryAppender : log4net.Appender.AppenderSkeleton
{
public SentryAppender() { }
public string? Dsn { get; set; }
public string? Environment { get; set; }
public log4net.Core.Level? MinimumEventLevel { get; set; }
public bool SendIdentity { get; set; }
protected override void Append(log4net.Core.LoggingEvent loggingEvent) { }
protected override void OnClose() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Sentry.Log4Net
public SentryAppender() { }
public string? Dsn { get; set; }
public string? Environment { get; set; }
public log4net.Core.Level? MinimumEventLevel { get; set; }
public bool SendIdentity { get; set; }
protected override void Append(log4net.Core.LoggingEvent loggingEvent) { }
protected override void OnClose() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Sentry.Log4Net
public SentryAppender() { }
public string? Dsn { get; set; }
public string? Environment { get; set; }
public log4net.Core.Level? MinimumEventLevel { get; set; }
public bool SendIdentity { get; set; }
protected override void Append(log4net.Core.LoggingEvent loggingEvent) { }
protected override void OnClose() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Sentry.Log4Net
public SentryAppender() { }
public string? Dsn { get; set; }
public string? Environment { get; set; }
public log4net.Core.Level? MinimumEventLevel { get; set; }
public bool SendIdentity { get; set; }
protected override void Append(log4net.Core.LoggingEvent loggingEvent) { }
protected override void OnClose() { }
Expand Down
88 changes: 88 additions & 0 deletions test/Sentry.Log4Net.Tests/SentryAppenderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ private class Fixture
public IDisposable SdkDisposeHandle { get; set; } = Substitute.For<IDisposable>();
public Func<string, IDisposable> InitAction { get; set; }
public IHub Hub { get; set; } = Substitute.For<IHub>();
public Func<IHub> HubAccessor { get; set; }
public Scope Scope { get; } = new(new SentryOptions());
public string Dsn { get; set; } = "dsn";

public Fixture()
{
HubAccessor = () => Hub;
Hub.ConfigureScope(Arg.Invoke(Scope));
InitAction = s =>
{
DsnReceivedOnInit = s;
Expand Down Expand Up @@ -125,6 +129,20 @@ public void Append_NullEvent_NoOp()
sut.DoAppend(null as LoggingEvent);
}

[Fact]
public void Append_BelowThreshold_DoesNotSendEvent()
{
var sut = _fixture.GetSut();
sut.Threshold = Level.Warn;
var evt = new LoggingEvent(new LoggingEventData
{
Level = Level.Info
});

sut.DoAppend(evt);
jamescrosswell marked this conversation as resolved.
Show resolved Hide resolved
_fixture.Hub.DidNotReceiveWithAnyArgs().CaptureEvent(Arg.Any<SentryEvent>());
}

[Fact]
public void Append_ByDefault_DoesNotSetUser()
{
Expand Down Expand Up @@ -291,6 +309,76 @@ public void Append_ConfiguredEnvironment()
.CaptureEvent(Arg.Is<SentryEvent>(e => e.Environment == expected));
}

[Fact]
public void MinimumEventLevel_DefaultsToNull()
{
var appender = new SentryAppender();
Assert.Null(appender.MinimumEventLevel);
}

[Fact]
public void DoAppend_BelowMinimumEventLevel_AddsBreadcrumb()
{
var sut = _fixture.GetSut();
sut.Threshold = Level.Debug;
sut.MinimumEventLevel = Level.Error;

const string expectedBreadcrumbMsg = "log4net breadcrumb";
var warnEvt = new LoggingEvent(new LoggingEventData
{
Level = Level.Warn,
Message = expectedBreadcrumbMsg
});
sut.DoAppend(warnEvt);

var breadcrumb = _fixture.Scope.Breadcrumbs.First();
Assert.Equal(expectedBreadcrumbMsg, breadcrumb.Message);
}

[Fact]
public void DoAppend_NullMinimumEventLevel_AddsEvent()
{
var sut = _fixture.GetSut();
sut.Threshold = Level.Debug;
sut.MinimumEventLevel = null;

const string expectedMessage = "log4net message";
var warnEvt = new LoggingEvent(new LoggingEventData
{
Level = Level.Warn,
Message = expectedMessage
});
sut.DoAppend(warnEvt);

// No breadcrumb is added.
Assert.Empty(_fixture.Scope.Breadcrumbs);
// Event is sent instead.
_ = _fixture.Hub.Received(1)
.CaptureEvent(Arg.Is<SentryEvent>(e => e.Message.Message == expectedMessage));
}

[Fact]
public void DoAppend_AboveMinimumEventLevel_AddsEvent()
{
var sut = _fixture.GetSut();
sut.Threshold = Level.Debug;
sut.MinimumEventLevel = Level.Warn;

const string expectedMessage = "log4net message";
var warnEvt = new LoggingEvent(new LoggingEventData
{
Level = Level.Error,
Message = expectedMessage
});
sut.DoAppend(warnEvt);

// No breadcrumb is added.
Assert.Empty(_fixture.Scope.Breadcrumbs);
// Event is sent instead.
_ = _fixture.Hub.Received(1)
.CaptureEvent(Arg.Is<SentryEvent>(e => e.Message.Message == expectedMessage));
}

[Fact]
public void Close_DisposesSdk()
{
Expand Down
Loading