From 8aae085ca1df264fa47204beb2f456e5e3a01539 Mon Sep 17 00:00:00 2001 From: Vitalii Mikhailov Date: Sat, 13 Apr 2024 00:50:39 +0300 Subject: [PATCH] Switched to inner exceptions Fixed exceptions Better analyzer namings --- ...NexusMods.Server.ValueObjects.Vogen.csproj | 1 + .../Models/ExceptionTypeId.cs | 11 +++ .../BUTR.Site.NexusMods.Server.csproj | 4 +- .../Contexts/EntityFactory.cs | 7 -- .../CrashReportsAnalyzerController.cs | 72 ++++++++++--------- .../Models/Database/ExceptionTypeEntity.cs | 20 ------ .../General/ICrashReportBatchedHandler.cs | 2 +- .../ApiResultProblemDetailsWriter.cs | 10 ++- 8 files changed, 62 insertions(+), 65 deletions(-) diff --git a/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj b/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj index 9fc9fcde..7be481a3 100644 --- a/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj +++ b/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/BUTR.Site.NexusMods.Server.ValueObjects.Vogen.csproj @@ -8,6 +8,7 @@ + diff --git a/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/Models/ExceptionTypeId.cs b/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/Models/ExceptionTypeId.cs index 4481a767..1d631a4e 100644 --- a/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/Models/ExceptionTypeId.cs +++ b/src/BUTR.Site.NexusMods.Server.ValueObjects.Vogen/Models/ExceptionTypeId.cs @@ -1,3 +1,5 @@ +using BUTR.CrashReport.Models; + namespace BUTR.Site.NexusMods.Server.Models; using TType = ExceptionTypeId; @@ -12,6 +14,15 @@ namespace BUTR.Site.NexusMods.Server.Models; public static bool IsInitialized(TType instance) => instance._isInitialized; public static TType DeserializeDangerous(TValueType instance) => Deserialize(instance); + public static TType FromException(ExceptionModel exception) + { + var exc = exception; + while (exc.InnerException is not null) + exc = exc.InnerException; + + return From(exc.Type); + } + public static int GetHashCode(TType instance) => VogenDefaults.GetHashCode(instance); public static bool Equals(TType left, TType right) => VogenDefaults.Equals(left, right); diff --git a/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj b/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj index 01892770..cda62e29 100644 --- a/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj +++ b/src/BUTR.Site.NexusMods.Server/BUTR.Site.NexusMods.Server.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/src/BUTR.Site.NexusMods.Server/Contexts/EntityFactory.cs b/src/BUTR.Site.NexusMods.Server/Contexts/EntityFactory.cs index 3a4f3c64..ccbf9a6d 100644 --- a/src/BUTR.Site.NexusMods.Server/Contexts/EntityFactory.cs +++ b/src/BUTR.Site.NexusMods.Server/Contexts/EntityFactory.cs @@ -177,13 +177,6 @@ public ExceptionTypeEntity GetOrCreateExceptionType(ExceptionTypeId exception) static ExceptionTypeEntity ValueFactory(ExceptionTypeId id, TenantId tenant) => ExceptionTypeEntity.Create(tenant, id); } - public ExceptionTypeEntity GetOrCreateExceptionTypeFromException(string exception) - { - var tenant = _tenantContextAccessor.Current; - return _exceptionTypes.GetOrAdd(ExceptionTypeEntity.FromException(tenant, exception).ExceptionTypeId, ValueFactory, tenant); - - static ExceptionTypeEntity ValueFactory(ExceptionTypeId id, TenantId tenant) => ExceptionTypeEntity.Create(tenant, id); - } public async Task SaveCreatedAsync(CancellationToken ct) { diff --git a/src/BUTR.Site.NexusMods.Server/Controllers/CrashReportsAnalyzerController.cs b/src/BUTR.Site.NexusMods.Server/Controllers/CrashReportsAnalyzerController.cs index ccc00ed8..0f2ab912 100644 --- a/src/BUTR.Site.NexusMods.Server/Controllers/CrashReportsAnalyzerController.cs +++ b/src/BUTR.Site.NexusMods.Server/Controllers/CrashReportsAnalyzerController.cs @@ -7,7 +7,6 @@ using BUTR.Site.NexusMods.Server.Models.Database; using BUTR.Site.NexusMods.Server.Utils; using BUTR.Site.NexusMods.Server.Utils.BindingSources; -using BUTR.Site.NexusMods.Shared.Helpers; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -67,7 +66,7 @@ public CrashReportsAnalyzerController(ILogger lo private async IAsyncEnumerable GetModuleSuggestionsAsync(CrashReportModel crashReport, [EnumeratorCancellation] CancellationToken ct) { - static bool GetTypeLoadExceptionLevel(ExceptionModel? exceptionModel, ref int level, CancellationToken ct) + static bool GetABIException(ref ExceptionModel? exceptionModel, CancellationToken ct) { if (ct.IsCancellationRequested) return false; @@ -78,28 +77,30 @@ static bool GetTypeLoadExceptionLevel(ExceptionModel? exceptionModel, ref int le if (exceptionModel.Type == "System.TypeLoadException") return true; - level++; - return GetTypeLoadExceptionLevel(exceptionModel.InnerException, ref level, ct); + if (exceptionModel.Type == "System.MissingFieldException") + return true; + + if (exceptionModel.Type == "System.MissingMemberException") + return true; + + if (exceptionModel.Type == "System.MissingMethodException") + return true; + + exceptionModel = exceptionModel.InnerException; + return GetABIException(ref exceptionModel, ct); } await Task.Yield(); - // - var level = 1; - if (GetTypeLoadExceptionLevel(crashReport.Exception, ref level, ct)) + var exception = crashReport.Exception; + if (GetABIException(ref exception, ct) && exception?.SourceModuleId is { } moduleId) { - var involvedModule = crashReport.InvolvedModules[level]; yield return new ModuleSuggestedFix { - ModuleId = involvedModule.ModuleOrLoaderPluginId, + ModuleId = moduleId, Type = ModuleSuggestedFixType.Update, }; } - // - - //var callStackLines = ex.CallStack.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); - //var firstCallStackLine = callStackLines[0].Trim(); - //var stacktrace = crashReport.EnhancedStacktrace.FirstOrDefault(x => firstCallStackLine == $"at {x.Name}"); } private async IAsyncEnumerable GetModuleUpdatesForBannerlordAsync(CrashReportModel crashReport, [EnumeratorCancellation] CancellationToken ct) @@ -108,27 +109,28 @@ private async IAsyncEnumerable GetModuleUpdatesForBannerlordAsync( if (!ApplicationVersion.TryParse(crashReport.Metadata.GameVersion, out var gVersion)) yield break; - var moduleIds = crashReport.Modules.Where(x => !x.IsOfficial && string.IsNullOrEmpty(x.Url) && x.UpdateInfo is null) + var currentModuleIdsWithoutAnyData = crashReport.Modules.Where(x => !x.IsOfficial && string.IsNullOrEmpty(x.Url) && x.UpdateInfo is null) .Select(x => ModuleId.From(x.Id)).ToArray(); - var nexusModsIds = crashReport.Modules.Where(x => !x.IsOfficial && !string.IsNullOrEmpty(x.Url)) + var currentMexusModsUpdateInfos = crashReport.Modules.Where(x => !x.IsOfficial && x.UpdateInfo is { Value: "NexusMods" }) + .Select(x => NexusModsModId.TryParse(x.UpdateInfo!.Value, out var modId) ? modId : NexusModsModId.None) + .Where(x => x != NexusModsModId.None).ToArray(); + var currentNexusModsIds = crashReport.Modules.Where(x => !x.IsOfficial && !string.IsNullOrEmpty(x.Url)) .Select(x => NexusModsModId.TryParseUrl(x.Url, out var modId) ? modId : NexusModsModId.None) .Where(x => x != NexusModsModId.None).ToArray(); - var nexusModsIds2 = crashReport.Modules.Where(x => !x.IsOfficial && !string.IsNullOrEmpty(x.Url)) - .Select(x => new { ModuelId = ModuleId.From(x.Id), NexusModsId = NexusModsModId.From(NexusModsUtils.TryParse(x.Url, out _, out var id) ? (int) id : -1) }) - .Where(x => x.NexusModsId != NexusModsModId.None).ToArray(); - var updateInfos = crashReport.Modules.Where(x => !x.IsOfficial && x.UpdateInfo is not null) - .Select(x => new { ModuleId = ModuleId.From(x.Id), x.UpdateInfo }).ToArray(); - var moduleIdVersions = crashReport.Modules + + var currentModules = crashReport.Modules .Where(x => !x.IsOfficial).Select(x => new { ModuleId = ModuleId.From(x.Id), Version = ModuleVersion.From(x.Version) }).ToArray(); // SMAPI uses different update providers - Chucklefish, NexusMods, GitHub // We curectly will only use NexusMods - var moduleIdEntries = _dbContextRead.NexusModsModToModuleInfoHistory.Where(x => nexusModsIds.Contains(x.NexusModsMod.NexusModsModId)); - var nexusModsIdEntries = _dbContextRead.NexusModsModToModuleInfoHistory.Where(x => moduleIds.Contains(x.Module.ModuleId)); //var updateInfoEntries = _dbContextRead.NexusModsModToModuleInfoHistory.Where(x => moduleIds.Contains(x.Module.ModuleId)); //var entries = _dbContextRead.NexusModsModToModuleInfoHistory.Where(x => moduleIds.Contains(x.Module.ModuleId)); - var entries = moduleIdEntries.Concat(nexusModsIdEntries); - var compatibleWithGameVersion = entries.AsEnumerable().Select(x => new + var historicEntriesBasedOnModuleId = _dbContextRead.NexusModsModToModuleInfoHistory.Where(x => currentModuleIdsWithoutAnyData.Contains(x.Module.ModuleId)); + var historicEntriesBasedOnNexusModsId = _dbContextRead.NexusModsModToModuleInfoHistory.Where(x => currentNexusModsIds.Contains(x.NexusModsMod.NexusModsModId)); + var historicEntriesBasedOnUpdateInfo = _dbContextRead.NexusModsModToModuleInfoHistory.Where(x => currentMexusModsUpdateInfos.Contains(x.NexusModsMod.NexusModsModId)); + var allHistoricEntries = historicEntriesBasedOnModuleId.Concat(historicEntriesBasedOnNexusModsId).Concat(historicEntriesBasedOnUpdateInfo); + + var historicEntriesCompatibleWithGameVersion = allHistoricEntries.AsEnumerable().Select(x => new { ModuleId = x.Module.ModuleId, ModuleVersion = ApplicationVersion.TryParse(x.ModuleVersion.Value, out var v) ? v : ApplicationVersion.Empty, @@ -147,11 +149,15 @@ private async IAsyncEnumerable GetModuleUpdatesForBannerlordAsync( if (y.Version == ApplicationVersion.Empty && y.VersionRange == ApplicationVersionRange.Empty) return false; return (y.Version != ApplicationVersion.Empty && y.Version <= gVersion) || (y.VersionRange != ApplicationVersionRange.Empty && y.VersionRange.Min <= gVersion && y.VersionRange.Max > gVersion); - })).GroupBy(x => x.ModuleId).Select(x => x.MaxBy(y => y.ModuleVersion, new ApplicationVersionComparer())!).ToArray(); + })).ToArray(); + var latestModulesCompatibleWithGameVersions = historicEntriesCompatibleWithGameVersion + .GroupBy(x => x.ModuleId) + .Select(x => x.MaxBy(y => y.ModuleVersion, new ApplicationVersionComparer())!) + .ToArray(); - var latestUpdates = compatibleWithGameVersion.Where(x => + var latestUpdates = latestModulesCompatibleWithGameVersions.Where(x => { - var tuple = moduleIdVersions.FirstOrDefault(y => y.ModuleId == x.ModuleId); + var tuple = currentModules.FirstOrDefault(y => y.ModuleId == x.ModuleId); if (tuple is null) return false; @@ -160,13 +166,13 @@ private async IAsyncEnumerable GetModuleUpdatesForBannerlordAsync( return false; }).ToArray(); - foreach (var x in latestUpdates) + foreach (var update in latestUpdates) { yield return new ModuleUpdate { - ModuleId = x.ModuleId.Value, - ModuleVersion = x.ModuleVersion.ToString(), - IsModuleInvolved = crashReport.InvolvedModules.Any(y => y.ModuleOrLoaderPluginId == x.ModuleId) + ModuleId = update.ModuleId.Value, + ModuleVersion = update.ModuleVersion.ToString(), + IsModuleInvolved = crashReport.InvolvedModules.Any(y => y.ModuleOrLoaderPluginId == update.ModuleId) }; } } diff --git a/src/BUTR.Site.NexusMods.Server/Models/Database/ExceptionTypeEntity.cs b/src/BUTR.Site.NexusMods.Server/Models/Database/ExceptionTypeEntity.cs index 7c56e99a..9fff201d 100644 --- a/src/BUTR.Site.NexusMods.Server/Models/Database/ExceptionTypeEntity.cs +++ b/src/BUTR.Site.NexusMods.Server/Models/Database/ExceptionTypeEntity.cs @@ -25,24 +25,4 @@ private ExceptionTypeEntity() { } private ExceptionTypeEntity(TenantId tenant, ExceptionTypeId exceptionTypeId) : this() => (TenantId, ExceptionTypeId) = (tenant, exceptionTypeId); public static ExceptionTypeEntity Create(TenantId tenant, ExceptionTypeId exceptionTypeId) => new(tenant, exceptionTypeId); - public static ExceptionTypeEntity FromException(TenantId tenant, string exception) - { - var exceptionType = "NO_EXCEPTION"; - Span dest = stackalloc Range[32]; - var idx = 0; - foreach (ReadOnlySpan line in exception.SplitLines()) - { - if (idx < 3) - { - idx++; - continue; - } - - var count = line.Split(dest, ':'); - if (count < 2) break; - exceptionType = line[dest[1]].Trim().ToString(); - break; - } - return new() { TenantId = tenant, ExceptionTypeId = ExceptionTypeId.From(exceptionType) }; - } } \ No newline at end of file diff --git a/src/BUTR.Site.NexusMods.Server/Services/General/ICrashReportBatchedHandler.cs b/src/BUTR.Site.NexusMods.Server/Services/General/ICrashReportBatchedHandler.cs index 2739706d..f92b6c2c 100644 --- a/src/BUTR.Site.NexusMods.Server/Services/General/ICrashReportBatchedHandler.cs +++ b/src/BUTR.Site.NexusMods.Server/Services/General/ICrashReportBatchedHandler.cs @@ -255,7 +255,7 @@ private async Task WriteCrashReportsToDatabaseAsync(CancellationToken ct) Url = CrashReportUrl.From(new Uri(new Uri(_options.Endpoint), fileId.ToString())), Version = CrashReportVersion.From(report.Version), GameVersion = GameVersion.From(report.Metadata.GameVersion), - ExceptionType = entityFactory.GetOrCreateExceptionType(ExceptionTypeId.From(report.Exception.Type)), + ExceptionType = entityFactory.GetOrCreateExceptionType(ExceptionTypeId.FromException(report.Exception)), Exception = GetException(report.Exception), CreatedAt = fileId.Value.Length == 8 ? DateTimeOffset.UnixEpoch.ToUniversalTime() : date.ToUniversalTime(), }); diff --git a/src/BUTR.Site.NexusMods.Server/Utils/Http/ApiResults/ApiResultProblemDetailsWriter.cs b/src/BUTR.Site.NexusMods.Server/Utils/Http/ApiResults/ApiResultProblemDetailsWriter.cs index cbca2289..b694d7e3 100644 --- a/src/BUTR.Site.NexusMods.Server/Utils/Http/ApiResults/ApiResultProblemDetailsWriter.cs +++ b/src/BUTR.Site.NexusMods.Server/Utils/Http/ApiResults/ApiResultProblemDetailsWriter.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -20,8 +21,11 @@ public ApiResultProblemDetailsWriter(IActionResultExecutor actionR public async ValueTask WriteAsync(ProblemDetailsContext context) { + var endpoint = context.HttpContext.GetEndpoint() ?? + context.HttpContext.Features.Get()?.Endpoint; + var routeData = context.HttpContext.GetRouteData(); - var action = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata(); + var action = endpoint!.Metadata.GetMetadata(); if (action is null) return; var actionContext = new ActionContext(context.HttpContext, routeData, action); var result = ApiResult.FromError(context.HttpContext, context.ProblemDetails); @@ -30,7 +34,9 @@ public async ValueTask WriteAsync(ProblemDetailsContext context) public bool CanWrite(ProblemDetailsContext problemDetailsContext) { - var endpoint = problemDetailsContext.HttpContext.GetEndpoint(); + var endpoint = problemDetailsContext.HttpContext.GetEndpoint() ?? + problemDetailsContext.HttpContext.Features.Get()?.Endpoint; + if (endpoint?.Metadata.GetMetadata() is { } context) return ApiResultUtils.IsReturnTypeApiResult(context.MethodInfo);