protected override void LoadComplete() { base.LoadComplete(); GlobalStatistics.StatisticsChanged += (_, e) => { switch (e.Action) { case NotifyCollectionChangedAction.Add: add(e.NewItems.Cast <IGlobalStatistic>()); break; case NotifyCollectionChangedAction.Remove: remove(e.OldItems.Cast <IGlobalStatistic>()); break; } }; add(GlobalStatistics.GetStatistics()); State.BindValueChanged(visibilityChanged, true); }
private void processLogEntry(LogEntry entry) { if (entry.Level < LogLevel.Verbose) { return; } var exception = entry.Exception; if (exception != null) { if (!shouldSubmitException(exception)) { return; } // 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), }, scope => { var beatmap = game.Dependencies.Get <IBindable <WorkingBeatmap> >().Value.BeatmapInfo; var ruleset = game.Dependencies.Get <IBindable <RulesetInfo> >().Value; scope.Contexts[@"config"] = new { Game = game.Dependencies.Get <OsuConfigManager>().GetLoggableState() // TODO: add framework config here. needs some consideration on how to expose. }; game.Dependencies.Get <RealmAccess>().Run(realm => { scope.Contexts[@"realm"] = new { Counts = new { BeatmapSets = realm.All <BeatmapSetInfo>().Count(), Beatmaps = realm.All <BeatmapInfo>().Count(), Files = realm.All <RealmFile>().Count(), Rulesets = realm.All <RulesetInfo>().Count(), RulesetsAvailable = realm.All <RulesetInfo>().Count(r => r.Available), Skins = realm.All <SkinInfo>().Count(), } }; }); scope.Contexts[@"global statistics"] = GlobalStatistics.GetStatistics() .GroupBy(s => s.Group) .ToDictionary(g => g.Key, items => items.ToDictionary(i => i.Name, g => g.DisplayValue)); scope.Contexts[@"beatmap"] = new { Name = beatmap.ToString(), Ruleset = beatmap.Ruleset.InstantiationInfo, beatmap.OnlineID, }; scope.Contexts[@"ruleset"] = new { ruleset.ShortName, ruleset.Name, ruleset.InstantiationInfo, ruleset.OnlineID }; scope.Contexts[@"clocks"] = new { Audio = game.Dependencies.Get <MusicController>().CurrentTrack.CurrentTime, Game = game.Clock.CurrentTime, }; scope.SetTag(@"beatmap", $"{beatmap.OnlineID}"); scope.SetTag(@"ruleset", ruleset.ShortName); scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})"); scope.SetTag(@"processor count", Environment.ProcessorCount.ToString()); }); } else { SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "navigation", level: getBreadcrumbLevel(entry.Level)); } }