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);
        }
Example #2
0
        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));
            }
        }