1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 16:27:26 +08:00
osu-lazer/osu.Game/Utils/SentryLogger.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

188 lines
6.2 KiB
C#
Raw Normal View History

// 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.
2018-08-03 18:25:55 +08:00
#nullable enable
2018-08-03 18:25:55 +08:00
using System;
using System.Diagnostics;
2018-10-31 15:43:35 +08:00
using System.IO;
using System.Net;
using osu.Framework.Bindables;
2018-08-03 18:25:55 +08:00
using osu.Framework.Logging;
using osu.Game.Online.API.Requests.Responses;
2019-11-12 21:12:38 +08:00
using Sentry;
2022-05-10 14:07:02 +08:00
using Sentry.Protocol;
2018-08-03 18:25:55 +08:00
namespace osu.Game.Utils
{
/// <summary>
/// Report errors to sentry.
/// </summary>
2019-11-12 21:12:38 +08:00
public class SentryLogger : IDisposable
2018-08-03 18:25:55 +08:00
{
private IBindable<APIUser>? localUser;
private readonly IDisposable? sentrySession;
2019-11-12 21:12:38 +08:00
public SentryLogger(OsuGame game)
2018-08-03 18:25:55 +08:00
{
sentrySession = SentrySdk.Init(options =>
2019-11-12 21:12:38 +08:00
{
// Not setting the dsn will completely disable sentry.
if (game.IsDeployedBuild)
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
2019-11-21 21:55:31 +08:00
options.AutoSessionTracking = true;
options.IsEnvironmentUser = false;
options.Release = game.Version;
});
2019-11-12 22:16:48 +08:00
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);
}
2019-03-08 11:00:12 +08:00
private void processLogEntry(LogEntry entry)
{
if (entry.Level < LogLevel.Verbose) return;
2018-08-03 18:25:55 +08:00
var exception = entry.Exception;
if (exception != null)
{
if (!shouldSubmitException(exception)) return;
2022-05-10 14:07:02 +08:00
// 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
2022-05-10 14:19:43 +08:00
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;
2022-05-10 14:07:02 +08:00
SentrySdk.CaptureEvent(new SentryEvent(exception)
2022-05-10 13:08:42 +08:00
{
Message = entry.Message,
Level = getSentryLevel(entry.Level),
});
}
else
2022-05-10 15:14:04 +08:00
SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "navigation", level: getBreadcrumbLevel(entry.Level));
2018-08-03 18:25:55 +08:00
}
2022-05-10 15:14:04 +08:00
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)
2022-05-10 13:08:42 +08:00
{
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);
}
}
2019-07-30 16:52:06 +08:00
private bool shouldSubmitException(Exception exception)
{
switch (exception)
{
case IOException ioe:
// disk full exceptions, see https://stackoverflow.com/a/9294382
const int hr_error_handle_disk_full = unchecked((int)0x80070027);
const int hr_error_disk_full = unchecked((int)0x80070070);
if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full)
return false;
break;
case WebException we:
switch (we.Status)
{
// more statuses may need to be blocked as we come across them.
case WebExceptionStatus.Timeout:
return false;
}
break;
}
return true;
}
2018-08-03 18:25:55 +08:00
#region Disposal
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool isDisposing)
{
Logger.NewEntry -= processLogEntry;
sentrySession?.Dispose();
2018-08-03 18:25:55 +08:00
}
#endregion
}
}