diff --git a/Giants.Services/Core/ServicesModule.cs b/Giants.Services/Core/ServicesModule.cs index a9332ed..c9bd4d5 100644 --- a/Giants.Services/Core/ServicesModule.cs +++ b/Giants.Services/Core/ServicesModule.cs @@ -1,5 +1,7 @@ namespace Giants.Services { + using System; + using System.Net.Http.Headers; using Giants.Services.Core; using Giants.Services.Services; using Giants.Services.Store; @@ -22,6 +24,14 @@ services.AddHostedService(); services.AddHostedService(); + + services.AddHttpClient("Sentry", c => + { + c.BaseAddress = new Uri(configuration["SentryBaseUri"]); + + string sentryAuthenticationToken = configuration["SentryAuthenticationToken"]; + c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", sentryAuthenticationToken); + }); } } } diff --git a/Giants.Services/Giants.Services.csproj b/Giants.Services/Giants.Services.csproj index d80cc51..fe872a5 100644 --- a/Giants.Services/Giants.Services.csproj +++ b/Giants.Services/Giants.Services.csproj @@ -8,11 +8,13 @@ - - - - - + + + + + + + diff --git a/Giants.Services/Services/CrashReportService.cs b/Giants.Services/Services/CrashReportService.cs index f3b412d..7117a41 100644 --- a/Giants.Services/Services/CrashReportService.cs +++ b/Giants.Services/Services/CrashReportService.cs @@ -2,6 +2,9 @@ { using System; using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Net.Http; using System.Threading.Tasks; using Azure.Storage.Blobs; using Microsoft.Extensions.Configuration; @@ -12,18 +15,53 @@ private readonly BlobServiceClient blobServiceClient; private readonly IConfiguration configuration; private readonly ILogger logger; + private readonly IHttpClientFactory clientFactory; + + private const string SentryMinidumpUploadFileKey = "upload_file_minidump"; public CrashReportService( IConfiguration configuration, - ILogger logger) + ILogger logger, + IHttpClientFactory clientFactory) { this.configuration = configuration; this.logger = logger; + this.clientFactory = clientFactory; string blobConnectionString = configuration["BlobConnectionString"]; this.blobServiceClient = new BlobServiceClient(blobConnectionString); } + public async Task UploadMinidumpToSentry(string fileName, Stream stream) + { + string minidumpUri = this.configuration["SentryMinidumpUri"]; + if (string.IsNullOrEmpty(minidumpUri)) + { + throw new InvalidOperationException("Minidump URI is not defined."); + } + + var httpClient = this.clientFactory.CreateClient("Sentry"); + + using var zipArchive = new ZipArchive(stream); + var zipEntry = zipArchive.Entries.FirstOrDefault(e => e.Name == "crashdump.dmp"); + if (zipEntry == null) + { + throw new InvalidOperationException("No crash dump found in archive."); + } + + using var dumpStream = zipEntry.Open(); + using var formData = new MultipartFormDataContent + { + { new StreamContent(dumpStream), SentryMinidumpUploadFileKey, fileName } + }; + var response = await httpClient.PostAsync(minidumpUri, formData).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + throw new InvalidOperationException(); + } + } + public async Task ProcessReport(string fileName, string senderIpAddress, Stream stream) { this.logger.LogInformation("Processing crash report file {FileName} from {IP}", fileName, senderIpAddress); diff --git a/Giants.Services/Services/ICrashReportService.cs b/Giants.Services/Services/ICrashReportService.cs index 86d4130..52b39ab 100644 --- a/Giants.Services/Services/ICrashReportService.cs +++ b/Giants.Services/Services/ICrashReportService.cs @@ -5,6 +5,7 @@ public interface ICrashReportService { + Task UploadMinidumpToSentry(string fileName, Stream stream); Task ProcessReport(string fileName, string senderIpAddress, Stream stream); } } diff --git a/Giants.WebApi/Controllers/CrashReportsController.cs b/Giants.WebApi/Controllers/CrashReportsController.cs index eb491ef..865f69a 100644 --- a/Giants.WebApi/Controllers/CrashReportsController.cs +++ b/Giants.WebApi/Controllers/CrashReportsController.cs @@ -8,6 +8,8 @@ using Giants.Services.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; [ApiController] [Route("api/[controller]")] @@ -15,15 +17,20 @@ { private readonly ICrashReportService crashReportService; private readonly IHttpContextAccessor httpContextAccessor; - + private readonly ILogger logger; + private readonly IConfiguration configuration; private const long MaximumSizeInBytes = 5242880; // 5MB public CrashReportsController( ICrashReportService crashReportService, - IHttpContextAccessor httpContextAccessor) + IHttpContextAccessor httpContextAccessor, + ILogger logger, + IConfiguration configuration) { this.crashReportService = crashReportService; this.httpContextAccessor = httpContextAccessor; + this.logger = logger; + this.configuration = configuration; } [HttpPost] @@ -36,6 +43,18 @@ { await this.crashReportService.ProcessReport(file.FileName, this.GetRequestIpAddress(), stream).ConfigureAwait(false); } + + bool sentryEnabled = Convert.ToBoolean(this.configuration["SentryEnabled"]); + if (!sentryEnabled) + { + this.logger.LogInformation("Skipping Sentry upload; disabled."); + return; + } + + using (var stream = file.OpenReadStream()) + { + await this.crashReportService.UploadMinidumpToSentry(file.FileName, stream).ConfigureAwait(false); + } } private void ValidateFiles(IEnumerable formFiles)