diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index cc08e08653..19ceade644 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -4,7 +4,10 @@ using System; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IPC; @@ -20,6 +23,8 @@ namespace osu.Desktop using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) { + host.ExceptionThrown += handleException; + if (!host.IsPrimaryInstance) { var importer = new ArchiveImportIPCChannel(host); @@ -45,5 +50,22 @@ namespace osu.Desktop return 0; } } + + private static int allowableExceptions = 1; + + /// + /// Allow a maximum of one unhandled exception, per second of execution. + /// + /// + /// + private static bool handleException(Exception arg) + { + bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; + + Logger.Log($"Unhandled exception has been {(continueExecution ? "allowed" : "denied")} with {allowableExceptions} more allowable exceptions."); + + Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); + return continueExecution; + } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a1e385921f..6576529326 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -36,6 +36,8 @@ using osu.Game.Skinning; using OpenTK.Graphics; using osu.Game.Overlays.Volume; using osu.Game.Screens.Select; +using osu.Game.Utils; +using LogLevel = osu.Framework.Logging.LogLevel; namespace osu.Game { @@ -65,6 +67,8 @@ namespace osu.Game private ScreenshotManager screenshotManager; + protected RavenLogger RavenLogger; + public virtual Storage GetStorageForStableInstall() => null; private Intro intro @@ -108,6 +112,8 @@ namespace osu.Game this.args = args; forwardLoggedErrorsToNotifications(); + + RavenLogger = new RavenLogger(this); } public void ToggleSettings() => settings.ToggleVisibility(); @@ -152,6 +158,8 @@ namespace osu.Game dependencies.CacheAs(this); + dependencies.Cache(RavenLogger); + dependencies.CacheAs(ruleset); dependencies.CacheAs>(ruleset); @@ -273,6 +281,12 @@ namespace osu.Game menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay))); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + RavenLogger.Dispose(); + } + protected override void LoadComplete() { // this needs to be cached before base.LoadComplete as it is used by MenuCursorContainer. @@ -449,7 +463,7 @@ namespace osu.Game Schedule(() => notifications.Post(new SimpleNotification { Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb, - Text = entry.Message, + Text = entry.Message + (entry.Exception != null ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } else if (recentLogCount == short_term_display_limit) @@ -601,6 +615,7 @@ namespace osu.Game private void screenAdded(Screen newScreen) { currentScreen = (OsuScreen)newScreen; + Logger.Log($"Screen changed → {currentScreen}"); newScreen.ModePushed += screenAdded; newScreen.Exited += screenRemoved; @@ -609,6 +624,7 @@ namespace osu.Game private void screenRemoved(Screen newScreen) { currentScreen = (OsuScreen)newScreen; + Logger.Log($"Screen changed ← {currentScreen}"); if (newScreen == null) Exit(); diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs new file mode 100644 index 0000000000..aadb70add8 --- /dev/null +++ b/osu.Game/Utils/RavenLogger.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using osu.Framework.Logging; +using SharpRaven; +using SharpRaven.Data; + +namespace osu.Game.Utils +{ + /// + /// Report errors to sentry. + /// + public class RavenLogger : IDisposable + { + private readonly RavenClient raven = new RavenClient("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"); + + private readonly List tasks = new List(); + + public RavenLogger(OsuGame game) + { + raven.Release = game.Version; + + Logger.NewEntry += entry => + { + if (entry.Level < LogLevel.Verbose) return; + + if (entry.Exception != null) + queuePendingTask(raven.CaptureAsync(new SentryEvent(entry.Exception))); + else + raven.AddTrail(new Breadcrumb(entry.Target.ToString(), BreadcrumbType.Navigation) { Message = entry.Message }); + }; + } + + private void queuePendingTask(Task task) + { + lock (tasks) tasks.Add(task); + task.ContinueWith(_ => + { + lock (tasks) + tasks.Remove(task); + }); + } + + #region Disposal + + ~RavenLogger() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool isDisposed; + + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + return; + + isDisposed = true; + lock (tasks) Task.WaitAll(tasks.ToArray(), 5000); + } + + #endregion + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index eef586fd4c..da17500128 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,6 +21,7 @@ + \ No newline at end of file