Skip to content

Commit

Permalink
Sentry improvements
Browse files Browse the repository at this point in the history
1. Use Sentry's own backend
Still at testing phase, if any change happens it will be changed again to our self-hosted instance.
2. Add a way to disable via system environment variable
Setting env var "DISABLE_SENTRY" to true or 1 will disable exception handling
3. Omit IP address from being sent
4. Regionalized SentryHandler.cs
5. Updated PRIVACY.md
  • Loading branch information
bagusnl committed Nov 16, 2024
1 parent 2baa33d commit dfa2d43
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 47 deletions.
10 changes: 9 additions & 1 deletion CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,15 @@ private bool IsConsoleEnabled

private bool IsSendRemoteCrashData
{
get => SentryHelper.IsEnabled;
get
{
if (SentryHelper.IsDisableEnvVarDetected)
{
ToggleSendRemoteCrashData.IsEnabled = false;
ToolTipService.SetToolTip(ToggleSendRemoteCrashData, Lang._SettingsPage.Debug_SendRemoteCrashData_EnvVarDisablement);
}
return SentryHelper.IsEnabled;
}
set => SentryHelper.IsEnabled = value;
}

Expand Down
149 changes: 105 additions & 44 deletions Hi3Helper.Core/Classes/SentryHelper/SentryHelper.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Sentry;
using Sentry.Infrastructure;
using System;
using System.Threading;
using System.Threading.Tasks;
using Hi3Helper.Shared.Region;
using Sentry.Protocol;

// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable HeuristicUnreachableCode
// ReSharper disable RedundantIfElseBlock
Expand All @@ -13,10 +13,12 @@ namespace Hi3Helper.SentryHelper
{
public static class SentryHelper
{
#region Sentry Configuration

/// <summary>
/// DSN (Data Source Name a.k.a. upstream server) to be used for error reporting.
/// </summary>
private const string SentryDsn = "https://[email protected]/1";
private const string SentryDsn = "https://38df0c6a1a779a4d663def402084187f@o4508297437839360.ingest.de.sentry.io/4508302961410128";

/// <summary>
/// <inheritdoc cref="SentryOptions.MaxAttachmentSize"/>

Check warning on line 24 in Hi3Helper.Core/Classes/SentryHelper/SentryHelper.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Invalid XML documentation comment

Cannot resolve symbol 'SentryOptions'
Expand All @@ -26,9 +28,13 @@ public static class SentryHelper
/// <summary>
/// Whether to upload log file when exception is caught.
/// </summary>
private const bool SentryUploadLog = false;


private const bool SentryUploadLog = true;

#endregion


#region Enums

/// <summary>
/// Defines Exception Types to be used in Sentry's data reporting.
/// </summary>
Expand All @@ -48,10 +54,24 @@ public enum ExceptionType
Handled
}

#endregion

#region Enable/Disable Sentry

public static bool IsDisableEnvVarDetected => Convert.ToBoolean(Environment.GetEnvironmentVariable("DISABLE_SENTRY"));
private static bool? _isEnabled;
public static bool IsEnabled
{
get => _isEnabled ??= LauncherConfig.GetAppConfigValue("SendRemoteCrashData").ToBool();
get
{
if (IsDisableEnvVarDetected)
{
Logger.LogWriteLine("Detected 'DISABLE_SENTRY' environment variable! Disabling crash data reporter...");
LauncherConfig.SetAndSaveConfigValue("SendRemoteCrashData", false);
return false;
}
return _isEnabled ??= LauncherConfig.GetAppConfigValue("SendRemoteCrashData").ToBool();
}
set
{
if (value == _isEnabled) return;
Expand All @@ -70,38 +90,72 @@ public static bool IsEnabled
LauncherConfig.SetAndSaveConfigValue("SendRemoteCrashData", value);
}
}

#endregion


public static bool IsPreview { get; set; }
#region Initializer/Releaser

public static bool IsPreview { get; set; }

private static IDisposable? _sentryInstance;
public static void InitializeSentrySdk()
{
_sentryInstance = SentrySdk.Init(o =>
{
o.Dsn = SentryDsn;
o.AddEventProcessor(new SentryEventProcessor());
o.CacheDirectoryPath = LauncherConfig.AppDataFolder;
#if DEBUG
o.Debug = true;
o.DiagnosticLogger = new ConsoleAndTraceDiagnosticLogger(SentryLevel.Debug);
o.DiagnosticLevel = SentryLevel.Debug;
o.Distribution = "Debug";
#else
o.Debug = false;
o.DiagnosticLevel = SentryLevel.Error;
o.Distribution = IsPreview ? "Preview" : "Stable";
#endif
o.AutoSessionTracking = true;
o.StackTraceMode = StackTraceMode.Enhanced;
o.DisableSystemDiagnosticsMetricsIntegration();
o.TracesSampleRate = 1.0;
o.IsGlobalModeEnabled = true;
o.DisableWinUiUnhandledExceptionIntegration(); // Use this for trimmed/NativeAOT published app
o.StackTraceMode = StackTraceMode.Enhanced;
o.SendDefaultPii = false;
o.MaxAttachmentSize = SentryMaxAttachmentSize;
o.DeduplicateMode = DeduplicateMode.All;
});
_sentryInstance =
SentrySdk.Init(o =>
{
o.Dsn = SentryDsn;
o.AddEventProcessor(new SentryEventProcessor());
o.CacheDirectoryPath = LauncherConfig.AppDataFolder;
#if DEBUG
o.Debug = true;
o.DiagnosticLogger = new ConsoleAndTraceDiagnosticLogger(SentryLevel.Debug);
o.DiagnosticLevel = SentryLevel.Debug;
o.Distribution = "Debug";
#else
o.Debug = false;
o.DiagnosticLevel = SentryLevel.Error;
o.Distribution = IsPreview ? "Preview" : "Stable";
#endif
o.AutoSessionTracking = true;
o.StackTraceMode = StackTraceMode.Enhanced;
o.DisableSystemDiagnosticsMetricsIntegration();
#if DEBUG
// Set TracesSampleRate to 1.0 to capture 100%
// of transactions for tracing.
// We recommend adjusting this value in production.
o.TracesSampleRate = 1.0;

// Sample rate for profiling, applied on top of other TracesSampleRate,
// e.g. 0.2 means we want to profile 20 % of the captured transactions.
// We recommend adjusting this value in production.
o.ProfilesSampleRate = 1.0;
#else
// Set TracesSampleRate to 1.0 to capture 100%
// of transactions for tracing.
// We recommend adjusting this value in production.
o.TracesSampleRate = 0.4;

// Sample rate for profiling, applied on top of other TracesSampleRate,
// e.g. 0.2 means we want to profile 20 % of the captured transactions.
// We recommend adjusting this value in production.
o.ProfilesSampleRate = 0.2;
#endif
o.IsGlobalModeEnabled = true;
o.DisableWinUiUnhandledExceptionIntegration(); // Use this for trimmed/NativeAOT published app
o.StackTraceMode = StackTraceMode.Enhanced;
o.SendDefaultPii = false;
o.MaxAttachmentSize = SentryMaxAttachmentSize;
o.DeduplicateMode = DeduplicateMode.All;
o.Release = LauncherConfig.AppCurrentVersionString;
});
SentrySdk.ConfigureScope(s =>
{
s.User = new SentryUser
{
IpAddress = null // Do not send user IP address.
};
});
}

/// <summary>
Expand All @@ -118,7 +172,7 @@ public static void StopSentrySdk()
catch (Exception ex)
{
Logger.LogWriteLine($"Failed when preparing to stop SentryInstance, Dispose will still be invoked!\r\n{ex}"
, LogType.Error, true);
, LogType.Error, true);
}
finally
{
Expand All @@ -129,22 +183,27 @@ public static void StopSentrySdk()
public static void InitializeExceptionRedirect()
{
AppDomain.CurrentDomain.UnhandledException += AppDomain_UnhandledExceptionEvent;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}

private static void ReleaseExceptionRedirect()

Check warning on line 189 in Hi3Helper.Core/Classes/SentryHelper/SentryHelper.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Method return value is never used (private accessibility)

Method 'ReleaseExceptionRedirect' return value is never used
{
AppDomain.CurrentDomain.UnhandledException -= AppDomain_UnhandledExceptionEvent;
TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException;
TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException;
}

#endregion


#region Exception Handlers

private static void AppDomain_UnhandledExceptionEvent(object sender, UnhandledExceptionEventArgs a)
{
// Handle any unhandled errors in app domain
// https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=net-9.0
var ex = a.ExceptionObject as Exception;
if (ex == null) return;
ex.Data[Mechanism.HandledKey] = false;
ex.Data[Mechanism.HandledKey] = false;
ex.Data[Mechanism.MechanismKey] = "Application.UnhandledException";
ExceptionHandler(ex, ExceptionType.UnhandledOther);

Expand Down Expand Up @@ -181,11 +240,11 @@ public static void ExceptionHandler(Exception ex, ExceptionType exT = ExceptionT
s.AddAttachment(LoggerBase.LogPath, AttachmentType.Default, "text/plain");
});
}
#pragma warning restore CS0162 // Unreachable code detected
else
{
SentrySdk.CaptureException(ex);
}
#pragma warning restore CS0162 // Unreachable code detected
}

/// <summary>
Expand All @@ -202,7 +261,7 @@ public static async Task ExceptionHandlerAsync(Exception ex, ExceptionType exT =
else if (exT == ExceptionType.UnhandledOther)
ex.Data[Mechanism.MechanismKey] = "Application.UnhandledException";
if (SentryUploadLog) // Upload log file if enabled
#pragma warning disable CS0162 // Unreachable code detected
#pragma warning disable CS0162 // Unreachable code detected
// ReSharper disable once HeuristicUnreachableCode
{
if ((bool)(ex.Data[Mechanism.HandledKey] ?? false))
Expand All @@ -213,16 +272,16 @@ public static async Task ExceptionHandlerAsync(Exception ex, ExceptionType exT =
s.AddAttachment(LoggerBase.LogPath, AttachmentType.Default, "text/plain");
});
}
#pragma warning restore CS0162 // Unreachable code detected
else
{
SentrySdk.CaptureException(ex);
}
#pragma warning restore CS0162 // Unreachable code detected

await SentrySdk.FlushAsync(TimeSpan.FromSeconds(10));
}

private static Exception? _exHLoopLastEx;
private static Exception? _exHLoopLastEx;
private static CancellationTokenSource _loopToken = new();

// ReSharper disable once AsyncVoidMethod
Expand Down Expand Up @@ -269,13 +328,13 @@ public static async Task ExceptionHandler_ForLoopAsync(Exception ex, ExceptionTy
if (ex == _exHLoopLastEx) return; // If exception pointer is the same as the last one, ignore it.
await _loopToken.CancelAsync(); // Cancel the previous loop
_loopToken.Dispose();
_loopToken = new CancellationTokenSource(); // Create new token
_loopToken = new CancellationTokenSource(); // Create new token
_exHLoopLastEx = ex;
ExHLoopLastEx_AutoClean(); // Start auto clean loop

Check warning on line 333 in Hi3Helper.Core/Classes/SentryHelper/SentryHelper.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Async method invocation without await expression

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

ex.Data[Mechanism.HandledKey] = exT == ExceptionType.Handled;
if (SentryUploadLog) // Upload log file if enabled
#pragma warning disable CS0162 // Unreachable code detected
#pragma warning disable CS0162 // Unreachable code detected
// ReSharper disable once HeuristicUnreachableCode
{
if ((bool)(ex.Data[Mechanism.HandledKey] ?? false))
Expand All @@ -286,11 +345,13 @@ public static async Task ExceptionHandler_ForLoopAsync(Exception ex, ExceptionTy
s.AddAttachment(LoggerBase.LogPath, AttachmentType.Default, "text/plain");
});
}
#pragma warning restore CS0162 // Unreachable code detected
else
{
SentrySdk.CaptureException(ex);
}
#pragma warning restore CS0162 // Unreachable code detected
}
}

#endregion
}
1 change: 1 addition & 0 deletions Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public sealed partial class LangSettingsPage
public string Debug_Console { get; set; } = LangFallback?._SettingsPage.Debug_Console;
public string Debug_IncludeGameLogs { get; set; } = LangFallback?._SettingsPage.Debug_IncludeGameLogs;
public string Debug_SendRemoteCrashData { get; set; } = LangFallback?._SettingsPage.Debug_SendRemoteCrashData;
public string Debug_SendRemoteCrashData_EnvVarDisablement { get; set; } = LangFallback?._SettingsPage.Debug_SendRemoteCrashData_EnvVarDisablement;
public string Debug_MultipleInstance { get; set; } = LangFallback?._SettingsPage.Debug_MultipleInstance;
public string ChangeRegionWarning_Toggle { get; set; } = LangFallback?._SettingsPage.ChangeRegionWarning_Toggle;
public string ChangeRegionInstant_Toggle { get; set; } = LangFallback?._SettingsPage.ChangeRegionInstant_Toggle;
Expand Down
1 change: 1 addition & 0 deletions Hi3Helper.Core/Lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@
"Debug_Console": "Show Console",
"Debug_IncludeGameLogs": "Save Game logs to Collapse's (might contain sensitive data)",
"Debug_SendRemoteCrashData": "Send anonymous crash reports to developers",
"Debug_SendRemoteCrashData_EnvVarDisablement": "This setting is disabled due to 'DISABLE_SENTRY' environment variable being set to true.",
"Debug_MultipleInstance": "Allow running multiple instances of Collapse",

"ChangeRegionWarning_Toggle": "Show Region Change Warning",
Expand Down
14 changes: 12 additions & 2 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Privacy Policy for Collapse Launcher

_Last Updated: November 11th, 2024_
_Last Updated: November 16th, 2024_

Thank you for using Collapse Launcher! This Privacy Policy outlines the information practices of Collapse Launcher and how any third-party services utilized may collect, use, and disclose data.

Expand All @@ -9,7 +9,7 @@ Please read this Privacy Policy carefully to understand how we may use and proce
## 1. Information We Do Not Collect

Collapse Launcher is committed to preserving your privacy and does not collect any personally identifiable information (PII). We do not track your usage, access, or any personal data related to your interaction with the program.
However, we do collect error data to aid in development. This data is stored on our self-hosted, Sentry-compatible dashboard server, which is accessible only to our core development team. We try our best to strip any PII data from being collected. You can disable this anytime by going to the application settings.
However, we do collect error data to aid in development. This data is stored on either self-hosted or SaaS, Sentry-compatible dashboard server, which is accessible only to our core development team. We try our best to strip any PII data from being collected. You can disable this anytime by going to the application settings.

## 2. Third-Party Services

Expand All @@ -20,6 +20,7 @@ While Collapse itself does not collect the information outlined above, we utiliz
- Information collected: IP address, device information, session information, date and time of request, cookies, geolocation information.
- Privacy policies:
https://docs.github.com/en/site-policy/privacy-policies
- Opt-out: You can change the CDN provider by going to the application settings.

#### Service Provider: Cloudflare, Inc.
- Purpose: Main Content Delivery Network (CDN) Provider
Expand All @@ -32,6 +33,15 @@ While Collapse itself does not collect the information outlined above, we utiliz
- Information collected: Monitoring Log (URL access path, IP address, user agent of the browser (OS type and version, CPU type, browser type and version, browser rendering engine, browser language and plug-in))
- Privacy policies:
https://www.tencentcloud.com/document/product/1133/45791
- Opt-out: You can change the CDN provider by going to the application settings.

#### Service Provider: Sentry (Functional Software, Inc.)
- Purpose: Error Tracking and Monitoring
- Information collected: Error data, Collapse log file, session information, environment information (device information, OS version, application version, etc.)
- Privacy policies:
https://sentry.io/privacy/
- Opt-out: You can disable this feature by going to the application settings or setting system environment variable "DISABLE_SENTRY" to true.


## 3. Cookies

Expand Down

0 comments on commit dfa2d43

Please sign in to comment.