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

WinUI doesn't report all crashes #3795

Open
masinette opened this issue Nov 22, 2024 · 3 comments
Open

WinUI doesn't report all crashes #3795

masinette opened this issue Nov 22, 2024 · 3 comments
Labels
Sync: Jira apply to auto-create a Jira shadow ticket

Comments

@masinette
Copy link
Member

masinette commented Nov 22, 2024

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<LoggingRule> 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 by Unito

@masinette masinette added the Sync: Jira apply to auto-create a Jira shadow ticket label Nov 22, 2024
@getsantry
Copy link

getsantry bot commented Nov 22, 2024

Assigning to @getsentry/support for routing ⏲️

@InterstellarStella InterstellarStella transferred this issue from getsentry/sentry Nov 26, 2024
@getsantry getsantry bot moved this from Waiting for: Support to Waiting for: Product Owner in GitHub Issues with 👀 3 Nov 26, 2024
@bruno-garcia
Copy link
Member

A repro would be helpful.

But we can try to poke at artificial way to cause those errors.

I suspect that these are native crashes and we don't capture them. sentry-native is enabled if compiling with Native AOT then all native crashes are captured but not sure how WinUI gets deployed, if it's already available for Native AOT or it's still .NET Native. Or something else.

@jamescrosswell for thoughts

@jamescrosswell
Copy link
Collaborator

I suspect that these are native crashes and we don't capture them. sentry-native is enabled if compiling with Native AOT then all native crashes are captured but not sure how WinUI gets deployed, if it's already available for Native AOT or it's still .NET Native. Or something else.

@jamescrosswell for thoughts

We currently only include sentry native on android, ios and maccatalyst targets:

<Import Project="Platforms\Native\Sentry.Native.targets"
Condition="'$(TargetPlatformIdentifier)' != 'android' and '$(TargetPlatformIdentifier)' != 'ios' and '$(TargetPlatformIdentifier)' != 'maccatalyst'"/>

It might be possible to include this for WinUI applications as well... providing the WinUI apps in question target net8.0 or later (so something like net8.0-windows10.0.19041.0) since Sentry's AOT support depends on various types that were only made available in net8.0. I don't know if WinUI apps can target net8.0-windows10.0.19041.0 though - I seem to recall last time I looked into WinUI it depends on .NET Native and has to target netstandard2.0... but my memory may not be perfect and that knowledge may be dated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Sync: Jira apply to auto-create a Jira shadow ticket
Projects
Status: No status
Status: No status
Development

No branches or pull requests

3 participants