Skip to content

Commit

Permalink
Merge pull request ppy#18205 from peppy/sentry-setup
Browse files Browse the repository at this point in the history
Bring sentry usage up-to-date
  • Loading branch information
smoogipoo authored May 10, 2022
2 parents 5af18f9 + 6a49eb6 commit a6674d8
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 22 deletions.
2 changes: 0 additions & 2 deletions osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
using osu.Game.Scoring;
using osu.Game.Screens.Menu;
using osu.Game.Skinning;
using osu.Game.Utils;

namespace osu.Game.Tests.Visual.Navigation
{
Expand All @@ -33,7 +32,6 @@ public class TestSceneOsuGame : OsuGameTestScene
private IReadOnlyList<Type> requiredGameDependencies => new[]
{
typeof(OsuGame),
typeof(SentryLogger),
typeof(OsuLogo),
typeof(IdleTracker),
typeof(OnScreenDisplay),
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/OsuGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ private void load()
{
dependencies.CacheAs(this);

dependencies.Cache(SentryLogger);
SentryLogger.AttachUser(API.LocalUser);

dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 });

Expand Down
123 changes: 104 additions & 19 deletions osu.Game/Utils/SentryLogger.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable enable

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using osu.Framework.Bindables;
using osu.Framework.Logging;
using osu.Game.Online.API.Requests.Responses;
using Sentry;
using Sentry.Protocol;

namespace osu.Game.Utils
{
Expand All @@ -14,26 +20,43 @@ namespace osu.Game.Utils
/// </summary>
public class SentryLogger : IDisposable
{
private SentryClient sentry;
private Scope sentryScope;
private Exception lastException;
private IBindable<APIUser>? localUser;

private readonly IDisposable? sentrySession;

public SentryLogger(OsuGame game)
{
if (!game.IsDeployedBuild) return;

var options = new SentryOptions
sentrySession = SentrySdk.Init(options =>
{
Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2",
Release = game.Version
};
// Not setting the dsn will completely disable sentry.
if (game.IsDeployedBuild)
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";

sentry = new SentryClient(options);
sentryScope = new Scope(options);
options.AutoSessionTracking = true;
options.IsEnvironmentUser = false;
options.Release = game.Version;
});

Logger.NewEntry += processLogEntry;
}

~SentryLogger() => Dispose(false);

public void AttachUser(IBindable<APIUser> user)
{
Debug.Assert(localUser == null);

localUser = user.GetBoundCopy();
localUser.BindValueChanged(u =>
{
SentrySdk.ConfigureScope(scope => scope.User = new User
{
Username = u.NewValue.Username,
Id = u.NewValue.Id.ToString(),
});
}, true);
}

private void processLogEntry(LogEntry entry)
{
if (entry.Level < LogLevel.Verbose) return;
Expand All @@ -44,14 +67,77 @@ private void processLogEntry(LogEntry entry)
{
if (!shouldSubmitException(exception)) return;

// since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports.
if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) return;

lastException = exception;
sentry.CaptureEvent(new SentryEvent(exception) { Message = entry.Message }, sentryScope);
// framework does some weird exception redirection which means sentry does not see unhandled exceptions using its automatic methods.
// but all unhandled exceptions still arrive via this pathway. we just need to mark them as unhandled for tagging purposes.
// easiest solution is to check the message matches what the framework logs this as.
// see https://github.com/ppy/osu-framework/blob/f932f8df053f0011d755c95ad9a2ed61b94d136b/osu.Framework/Platform/GameHost.cs#L336
bool wasUnhandled = entry.Message == @"An unhandled error has occurred.";
bool wasUnobserved = entry.Message == @"An unobserved error has occurred.";

if (wasUnobserved)
{
// see https://github.com/getsentry/sentry-dotnet/blob/c6a660b1affc894441c63df2695a995701671744/src/Sentry/Integrations/TaskUnobservedTaskExceptionIntegration.cs#L39
exception.Data[Mechanism.MechanismKey] = @"UnobservedTaskException";
}

if (wasUnhandled)
{
// see https://github.com/getsentry/sentry-dotnet/blob/main/src/Sentry/Integrations/AppDomainUnhandledExceptionIntegration.cs#L38-L39
exception.Data[Mechanism.MechanismKey] = @"AppDomain.UnhandledException";
}

exception.Data[Mechanism.HandledKey] = !wasUnhandled;

SentrySdk.CaptureEvent(new SentryEvent(exception)
{
Message = entry.Message,
Level = getSentryLevel(entry.Level),
});
}
else
sentryScope.AddBreadcrumb(DateTimeOffset.Now, entry.Message, entry.Target.ToString(), "navigation");
SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "navigation", level: getBreadcrumbLevel(entry.Level));
}

private BreadcrumbLevel getBreadcrumbLevel(LogLevel entryLevel)
{
switch (entryLevel)
{
case LogLevel.Debug:
return BreadcrumbLevel.Debug;

case LogLevel.Verbose:
return BreadcrumbLevel.Info;

case LogLevel.Important:
return BreadcrumbLevel.Warning;

case LogLevel.Error:
return BreadcrumbLevel.Error;

default:
throw new ArgumentOutOfRangeException(nameof(entryLevel), entryLevel, null);
}
}

private SentryLevel getSentryLevel(LogLevel entryLevel)
{
switch (entryLevel)
{
case LogLevel.Debug:
return SentryLevel.Debug;

case LogLevel.Verbose:
return SentryLevel.Info;

case LogLevel.Important:
return SentryLevel.Warning;

case LogLevel.Error:
return SentryLevel.Error;

default:
throw new ArgumentOutOfRangeException(nameof(entryLevel), entryLevel, null);
}
}

private bool shouldSubmitException(Exception exception)
Expand Down Expand Up @@ -93,8 +179,7 @@ public void Dispose()
protected virtual void Dispose(bool isDisposing)
{
Logger.NewEntry -= processLogEntry;
sentry = null;
sentryScope = null;
sentrySession?.Dispose();
}

#endregion
Expand Down

0 comments on commit a6674d8

Please sign in to comment.