Skip to content
Draft
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
3 changes: 2 additions & 1 deletion src/Sentry.Extensions.Logging/SentryLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public void Log<TState>(
CategoryName,
null,
data,
logLevel.ToBreadcrumbLevel());
logLevel.ToBreadcrumbLevel(),
exception is null ? null : new SentryHint(HintTypes.Exception, exception));
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/Sentry.Log4Net/SentryAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,16 @@ private void AddBreadcrumbFromLoggingEvent(LoggingEvent loggingEvent)
.Where(kvp => kvp.Value != null)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value!.ToString() ?? "");

_hub.AddBreadcrumb(message, category, type: null, data, level ?? default);
var exception = loggingEvent.ExceptionObject;

_hub.AddBreadcrumb(
clock: null,
message,
category,
type: null,
data,
level ?? default,
hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception));
return;
}

Expand Down
4 changes: 3 additions & 1 deletion src/Sentry.NLog/SentryTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,10 @@ private void CreateBreadcrumb(LogEventInfo logEvent, Exception? exception, bool
_clock,
message,
breadcrumbCategory,
type: null,
data: data,
level: logEvent.Level.ToBreadcrumbLevel());
level: logEvent.Level.ToBreadcrumbLevel(),
hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception));
}

private void CreateSentryEvent(LogEventInfo logEvent, Exception? exception, bool shouldIncludeProperties, IHub hub)
Expand Down
4 changes: 3 additions & 1 deletion src/Sentry.Serilog/SentrySink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,10 @@ private void InnerEmit(LogEvent logEvent)
? exception?.Message ?? ""
: formatted,
context,
type: null,
data: data,
level: logEvent.Level.ToBreadcrumbLevel());
level: logEvent.Level.ToBreadcrumbLevel(),
hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception));
}

// Read the options from the Hub, rather than the Sink's Serilog-Options, because 'EnableLogs' is declared in the base 'SentryOptions', rather than the derived 'SentrySerilogOptions'.
Expand Down
5 changes: 5 additions & 0 deletions src/Sentry/HintTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ public static class HintTypes
/// Used for HttpResponseMessage hints
/// </summary>
public const string HttpResponseMessage = "http-response-message";

/// <summary>
/// Used for the <see cref="System.Exception"/> that a breadcrumb was created from
/// </summary>
public const string Exception = "exception";
}
30 changes: 27 additions & 3 deletions src/Sentry/HubExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,32 @@ public static void AddBreadcrumb(
string? type = null,
IDictionary<string, string>? data = null,
BreadcrumbLevel level = default)
=> hub.AddBreadcrumb(clock, message, category, type, data, level, hint: null);

/// <summary>
/// Adds a breadcrumb using a custom <see cref="ISystemClock"/> which allows better testability.
/// </summary>
/// <param name="hub">The Hub which holds the scope stack.</param>
/// <param name="clock">The system clock.</param>
/// <param name="message">The message.</param>
/// <param name="category">Category.</param>
/// <param name="type">Breadcrumb type.</param>
/// <param name="data">Additional data.</param>
/// <param name="level">Breadcrumb level.</param>
/// <param name="hint">A hint provided with the breadcrumb in the BeforeBreadcrumb callback.</param>
/// <remarks>
/// This method is to be used by integrations to allow testing.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void AddBreadcrumb(
this IHub hub,
ISystemClock? clock,
string message,
string? category,
string? type,
IDictionary<string, string>? data,
BreadcrumbLevel level,
SentryHint? hint)
{
// Not to throw on code that ignores nullability warnings.
if (hub.IsNull())
Expand All @@ -207,9 +233,7 @@ public static void AddBreadcrumb(
level
);

hub.AddBreadcrumb(
breadcrumb
);
hub.AddBreadcrumb(breadcrumb, hint);
}

/// <summary>
Expand Down
14 changes: 13 additions & 1 deletion src/Sentry/Internal/Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,19 @@ private void AddBreadcrumbForException(SentryEvent evt, Scope scope)
{"exception_message", exceptionMessage}
};
}
scope.AddBreadcrumb(breadcrumbMessage, "Exception", data: data, level: BreadcrumbLevel.Fatal);

// Provide the original exception in a Hint, so that the BeforeBreadcrumb callback can filter or modify
// the breadcrumb based on the exception itself (e.g. by type) rather than by matching on its message.
var hint = new SentryHint(_options);
hint.Items[HintTypes.Exception] = exception;

var breadcrumb = new Breadcrumb(
message: breadcrumbMessage,
data: data,
category: "Exception",
level: BreadcrumbLevel.Fatal);

scope.AddBreadcrumb(breadcrumb, hint);
}
catch (Exception e)
{
Expand Down
20 changes: 20 additions & 0 deletions test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ public void Log_EventWithoutException_LeavesBreadcrumb()
_fixture.Scope.Breadcrumbs.Should().NotBeEmpty();
}

[Fact]
public void Log_BreadcrumbWithException_ProvidesExceptionInHint()
{
SentryHint hint = null;
_fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) =>
{
hint = h;
return breadcrumb;
});
var expectedException = new Exception("expected message");

var sut = _fixture.GetSut();

// LogLevel.Warning is below the default MinimumEventLevel, so only a breadcrumb is added
sut.Log<object>(LogLevel.Warning, default, null, expectedException, null);

hint.Should().NotBeNull();
hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException);
}

[Fact]
public void Log_WithEventId_EventIdAsTagOnEvent()
{
Expand Down
22 changes: 22 additions & 0 deletions test/Sentry.Log4Net.Tests/SentryAppenderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,28 @@ public void DoAppend_BelowMinimumEventLevel_AddsBreadcrumb()
Assert.Equal(expectedBreadcrumbMsg, breadcrumb.Message);
}

[Fact]
public void DoAppend_BreadcrumbWithException_ProvidesExceptionInHint()
{
SentryHint hint = null;
_fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) =>
{
hint = h;
return breadcrumb;
});
var expectedException = new Exception("expected");

var sut = _fixture.GetSut();
sut.Threshold = Level.Debug;
sut.MinimumEventLevel = Level.Error;

// Level.Warn is below the MinimumEventLevel, so only a breadcrumb is added
sut.DoAppend(new LoggingEvent(null, null, "logger", Level.Warn, "log4net breadcrumb", expectedException));

hint.Should().NotBeNull();
hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException);
}

[Fact]
public void DoAppend_NullMinimumEventLevel_AddsEvent()
{
Expand Down
21 changes: 21 additions & 0 deletions test/Sentry.NLog.Tests/SentryTargetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,27 @@ public void Log_WithoutException_LeavesBreadcrumb()
_fixture.Scope.Breadcrumbs.Should().NotBeEmpty();
}

[Fact]
public void Log_BreadcrumbWithException_ProvidesExceptionInHint()
{
SentryHint hint = null;
_fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) =>
{
hint = h;
return breadcrumb;
});
var expectedException = new Exception("expected");

_fixture.Options.MinimumEventLevel = LogLevel.Fatal;
var logger = _fixture.GetLogger();

// LogLevel.Error is below the MinimumEventLevel, so only a breadcrumb is added
logger.Error(expectedException, DefaultMessage);

hint.Should().NotBeNull();
hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException);
}

[Fact]
public void Log_WithException_CreatesEventWithException()
{
Expand Down
23 changes: 23 additions & 0 deletions test/Sentry.Serilog.Tests/SentrySinkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ public void EmitEvent_WithoutException_LeavesBreadcrumb()
_fixture.Scope.Breadcrumbs.Should().NotBeEmpty();
}

[Fact]
public void EmitBreadcrumb_WithException_ProvidesExceptionInHint()
{
SentryHint hint = null;
_fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) =>
{
hint = h;
return breadcrumb;
});
var expectedException = new Exception("expected message");

var sut = _fixture.GetSut();

// LogEventLevel.Warning is below the default MinimumEventLevel, so only a breadcrumb is added
var evt = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Warning, expectedException,
MessageTemplate.Empty, Enumerable.Empty<LogEventProperty>());

sut.Emit(evt);

hint.Should().NotBeNull();
hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException);
}

[Fact]
public void Emit_SerilogSdk_Name()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ namespace Sentry
public delegate bool HeapDumpTrigger(long usedMemory, long totalMemory);
public static class HintTypes
{
public const string Exception = "exception";
public const string HttpResponseMessage = "http-response-message";
}
public readonly struct HttpStatusCodeRange : System.IEquatable<Sentry.HttpStatusCodeRange>
Expand All @@ -149,6 +150,7 @@ namespace Sentry
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { }
public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary<string, string>? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action<Sentry.Scope> configureScope, Sentry.SentryHint? hint = null) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ namespace Sentry
public delegate bool HeapDumpTrigger(long usedMemory, long totalMemory);
public static class HintTypes
{
public const string Exception = "exception";
public const string HttpResponseMessage = "http-response-message";
}
public readonly struct HttpStatusCodeRange : System.IEquatable<Sentry.HttpStatusCodeRange>
Expand All @@ -149,6 +150,7 @@ namespace Sentry
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { }
public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary<string, string>? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action<Sentry.Scope> configureScope, Sentry.SentryHint? hint = null) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ namespace Sentry
public delegate bool HeapDumpTrigger(long usedMemory, long totalMemory);
public static class HintTypes
{
public const string Exception = "exception";
public const string HttpResponseMessage = "http-response-message";
}
public readonly struct HttpStatusCodeRange : System.IEquatable<Sentry.HttpStatusCodeRange>
Expand All @@ -149,6 +150,7 @@ namespace Sentry
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { }
public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary<string, string>? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action<Sentry.Scope> configureScope, Sentry.SentryHint? hint = null) { }
Expand Down
2 changes: 2 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ namespace Sentry
}
public static class HintTypes
{
public const string Exception = "exception";
public const string HttpResponseMessage = "http-response-message";
}
public readonly struct HttpStatusCodeRange : System.IEquatable<Sentry.HttpStatusCodeRange>
Expand All @@ -137,6 +138,7 @@ namespace Sentry
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { }
public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary<string, string>? data = null, Sentry.BreadcrumbLevel level = 0) { }
public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary<string, string>? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action<Sentry.Scope> configureScope) { }
public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action<Sentry.Scope> configureScope, Sentry.SentryHint? hint = null) { }
Expand Down
41 changes: 41 additions & 0 deletions test/Sentry.Tests/HubTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,47 @@ public void CaptureEvent_Exception_LeavesBreadcrumb(bool withScopeCallback)
breadcrumb.Category.Should().Be("Exception");
}

[Fact]
public void CaptureEvent_Exception_BreadcrumbHintContainsException()
{
// Arrange
SentryHint hint = null;
_fixture.Options.SetBeforeBreadcrumb((breadcrumb, h) =>
{
hint = h;
return breadcrumb;
});
using var hub = _fixture.GetSut();
var exception = new Exception("original");

// Act
hub.CaptureEvent(new SentryEvent(exception));

// Assert
hint.Should().NotBeNull();
hint.Items[HintTypes.Exception].Should().BeSameAs(exception);
}

[Fact]
public void CaptureEvent_Exception_BeforeBreadcrumbCanFilterOnExceptionType()
{
// Arrange
_fixture.Options.SetBeforeBreadcrumb((breadcrumb, hint) =>
hint.Items.TryGetValue(HintTypes.Exception, out var exception) && exception is InvalidOperationException
? null
: breadcrumb);
using var hub = _fixture.GetSut();
var scope = hub.ScopeManager.GetCurrent().Key;

// Act
hub.CaptureEvent(new SentryEvent(new InvalidOperationException("filtered")));
hub.CaptureEvent(new SentryEvent(new Exception("kept")));

// Assert
scope.Breadcrumbs.Should().ContainSingle(b => b.Category == "Exception")
.Which.Message.Should().Be("kept");
}

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