Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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.ToHint());
}
}

Expand Down
9 changes: 8 additions & 1 deletion src/Sentry.Log4Net/SentryAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,14 @@ 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);
_hub.AddBreadcrumb(
clock: null,
message,
category,
type: null,
data,
level ?? default,
hint: loggingEvent.ExceptionObject.ToHint());
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
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.ToHint());
}

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.ToHint());
}

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

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
9 changes: 9 additions & 0 deletions src/Sentry/SentryExceptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Sentry;
using Sentry.Internal;
using Sentry.Protocol;

Expand All @@ -7,6 +8,14 @@
[EditorBrowsable(EditorBrowsableState.Never)]
public static class SentryExceptionExtensions
{
/// <summary>
/// Creates a <see cref="SentryHint"/> carrying the exception that a breadcrumb was created from, or
/// <see langword="null"/> if there is no exception.
/// </summary>
internal static SentryHint? ToHint(this Exception? exception) => exception is null
? null
: new SentryHint(HintTypes.Exception, exception);

/// <summary>
/// Set a tag that will be added to the event when the exception is captured.
/// </summary>
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