getsentry / getsentry/sentry-dotnet

WinUI doesn't report all crashes

Open
#3,795 3 comments 0 reactions 0 assignees View on GitHub
.NET Improvement
Dominant language
C#
Stars
770
Forks
248
Avg merge
3d 4h
Merged PRs (30d)
49

Description

### Environment

SaaS (https://sentry.io/)

### What are you trying to accomplish?

(Org recently transitioned their WinUI app from using AppCenter)

Certain crashes are not appearing in Sentry.

System.Runtime.InteropServices.COMException (e.g. "The application called an interface that was marshalled for a different thread" with error code 0x8001010E) and AccessViolationExceptions aren’t being logged in Sentry.

Crashes are recorded in AppCenter after the app restarted, but do not appear in Sentry. Why aren't these types of exceptions being captured?

### How are you getting stuck?

They could not reproduce, but gave permission to post their configs here:

NLog CONFIGURATION:
```
public static class NLogConfig
{
public const string SentryTargetName = "Sentry";
public static readonly LogLevel MinimumSentryLevel = LogLevel.Warn;

public static void AddSentry(string sentryProjectDsn)
{
if (string.IsNullOrEmpty(sentryProjectDsn)) throw new ArgumentNullException(nameof(sentryProjectDsn));

AddGlobalConfiguration(() => AddSentryTarget(sentryProjectDsn));
}

private static void AddGlobalConfiguration(Action configure)
{
if (LogManager.Configuration == null)
{
LogManager.Configuration = new LoggingConfiguration();
}

configure();

LogManager.ReconfigExistingLoggers();
}

private static void AddSentryTarget(string sentryProjectDsn)
{
LogManager.Configuration.AddSentry(sentryProjectDsn, SentryTargetName, ConfigureSentryTarget);
FilterSentryTarget(SentryTargetName);
}

private static void ConfigureSentryTarget(SentryNLogOptions options)
{
options.InitializeSdk = false; // Sentry Sdk is initialized elsewhere

options.MinimumBreadcrumbLevel = LogLevel.Debug;
options.MinimumEventLevel = MinimumSentryLevel;

options.AddTag(SentryTags.Logger, "${logger}");
}

private static void FilterSentryTarget(string sentryTargetName)
{
Target sentryTarget = LogManager.Configuration.FindTargetByName(sentryTargetName);
IEnumerable sentryRules = LogManager.Configuration.LoggingRules.Where(r => r.Targets.Contains(sentryTarget));

foreach (LoggingRule sentryRule in sentryRules)
{
sentryRule.IgnoreOperationLogs();
sentryRule.IgnoreUnnecessaryQuartzLogs();
}
}

public static void IgnoreOperationLogs(this LoggingRule rule)
{
rule.Filters.Add(new WhenMethodFilter(e => e.Properties.ContainsKey(LoggingProperties.IsOperation) ? FilterResult.Ignore : FilterResult.Log));
}

public static void IgnoreUnnecessaryQuartzLogs(this LoggingRule rule)
{
rule.Filters.Add(new WhenMethodFilter(l => l.LoggerName.StartsWith("Quartz") && l.Level < LogLevel.Error ? FilterResult.Ignore : FilterResult.Log));
}
}
```
SENTRY CONFIGURATION:
```
public static class SentryConfigurator
{
public static void AddKobleConfiguration(this SentryOptions options, SentryConfiguration sentryConfiguration, IAppInfo appInfo)
{
var dataFilter = new CustomerDataFilter(() => appInfo.Environment, BlockedKeyFilter.Standard);

options.Dsn = sentryConfiguration.Dsn;
options.Environment = appInfo.Environment.ToString();
options.Release = GetRelease(sentryConfiguration.Project);
// Sentry docs indicate IsGlobalModeEnabled should be false for a server app and true for a client app.
// However, the docs don't describe much what it does. It determines whether updates to Sentry scopes
// (SentrySdk.PushScope(), etc.) will be applied to a global scope or to a scope in AsyncLocal storage
// (i.e. within a logical call context (ExecutionContext), meaning the scope will be different per
// ExecutionContext and will be implicitly pushed and popped in async methods and for different threads).
// NLog includes both global and AsyncLocal options, and both are useful. We have our own global state
// for Sentry (which is simpler to implement), so always set this property to false to enable AsyncLocal
// scope using Sentry's functionality. Also note that Sentry's global scope doesn't actually allow pushing
// so isn't fully functional, which is another reason to disable Sentry's global mode.
options.IsGlobalModeEnabled = false;
options.SetBeforeSend(e => OnBeforeSend(e, dataFilter));
options.AddEventProcessor(new RemoveUnnecessaryStacksEventProcessor());
}

private static string GetRelease(string project) => $"{project}@{BuildStamp.GIT_COMMIT_SHA}";

private static SentryEvent OnBeforeSend(SentryEvent sentryEvent, ILogDataFilter dataFilter)
{
// First amend data so we have the correct data to send
AmendData(sentryEvent, dataFilter);
// Now update the event's properties from the available data
UpdateEventFromData(sentryEvent);

return sentryEvent;
}

private static void AmendData(SentryEvent sentryEvent, ILogDataFilter dataFilter)
{
// Now that all the data is present, mask any of it that is likely sensitive
MaskSensitiveData(sentryEvent, dataFilter);

// Now that all the data is in its final form, we can remove properties that are duplicates of (valid) tags
sentryEvent.RemoveDuplicateProperties();
}

private static void MaskSensitiveData(SentryEvent sentryEvent, ILogDataFilter dataFilter)
{
var scrubber = new KeyScrubber(dataFilter);
var tagsAdapter = new SentryTagsMaskingAdapter(sentryEvent);
var extrasAdapter = new SentryExtrasMaskingAdapter(sentryEvent);

scrubber.MaskSensitiveKeys(tagsAdapter);
scrubber.MaskSensitiveKeys(extrasAdapter);
}

private static void UpdateEventFromData(SentryEvent sentryEvent)
{
SetUnobservedTaskToWarning(sentryEvent);
SetEnvironmentFromTag(sentryEvent);
SetUserFromTags(sentryEvent);
}

//REVIEW:workitem:24386: FW.Client.Logging: Turn up the nob to unobserved tasks - Remove this method and log normally
private static void SetUnobservedTaskToWarning(SentryEvent sentryEvent)
{
sentryEvent.Tags.TryGetValue(SentryTags.ApplicationType, out string applicationType);
if (applicationType == "Client")
{
var unobservedTaskException = sentryEvent.SentryExceptions?.FirstOrDefault(e => e.Mechanism.Type == "UnobservedTaskException");
if (unobservedTaskException != null)
sentryEvent.Level = SentryLevel.Warning;
}
}

private static void SetEnvironmentFromTag(SentryEvent sentryEvent)
{
sentryEvent.Tags.TryGetValue(SentryTags.Environment, out string tagEnvironment);

if (!string.IsNullOrEmpty(tagEnvironment))
{
sentryEvent.Environment = tagEnvironment;
}
}

private static void SetUserFromTags(SentryEvent sentryEvent)
{
sentryEvent.Tags.TryGetValue(SentryTags.UserName, out string userName);
if (!string.IsNullOrEmpty(userName))
{
sentryEvent.User.Username = userName;
}

sentryEvent.Tags.TryGetValue(SentryTags.UserID, out string userID);
if (!string.IsNullOrEmpty(userID))
{
sentryEvent.User.Id = userID;
}
}
}
```

### Where in the product are you?

Issues

### Link

_No response_

### DSN

_No response_

### Version

_No response_

┆Issue is synchronized with this [Jira Improvement](https://getsentry.atlassian.net/browse/FEEDBACK-2363) by [Unito](https://www.unito.io)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.