Skip to content

Commit e9c45fe

Browse files
authored
Include export status for every file (#201)
* gh-199 Include export status for every file * gh-199 Improve logging Signed-off-by: Victor Chang <[email protected]>
1 parent 358f400 commit e9c45fe

13 files changed

+161
-46
lines changed

src/Api/Monai.Deploy.InformaticsGateway.Api.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
</PackageReference>
3131
<PackageReference Include="Macross.Json.Extensions" Version="3.0.0" />
3232
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="6.0.9" />
33-
<PackageReference Include="Monai.Deploy.Messaging" Version="0.1.6" />
33+
<PackageReference Include="Monai.Deploy.Messaging" Version="0.1.7-rc0009" />
3434
<PackageReference Include="Monai.Deploy.Storage" Version="0.2.7" />
3535
</ItemGroup>
3636

src/Configuration/Monai.Deploy.InformaticsGateway.Configuration.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!--
1+
<!--
22
~ Copyright 2021-2022 MONAI Consortium
33
~
44
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,7 +30,7 @@
3030
</PackageReference>
3131
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
3232
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
33-
<PackageReference Include="Monai.Deploy.Messaging" Version="0.1.6" />
33+
<PackageReference Include="Monai.Deploy.Messaging" Version="0.1.7-rc0009" />
3434
<PackageReference Include="Monai.Deploy.Storage" Version="0.2.7" />
3535
<PackageReference Include="System.IO.Abstractions" Version="17.2.3" />
3636
</ItemGroup>

src/InformaticsGateway/Logging/Log.500.ExportService.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public static partial class Log
2828
[LoggerMessage(EventId = 501, Level = LogLevel.Warning, Message = "{ServiceName} paused due to insufficient storage space. Available storage space: {availableFreeSpace:D}.")]
2929
public static partial void ExportPausedDueToInsufficientStorageSpace(this ILogger logger, string serviceName, long availableFreeSpace);
3030

31-
[LoggerMessage(EventId = 502, Level = LogLevel.Warning, Message = "The export request {exportTaskId} is already queued for export.")]
32-
public static partial void ExportRequestAlreadyQueued(this ILogger logger, string exportTaskId);
31+
[LoggerMessage(EventId = 502, Level = LogLevel.Warning, Message = "Correlation ID={correlationId}. The export request {exportTaskId} is already queued for export.")]
32+
public static partial void ExportRequestAlreadyQueued(this ILogger logger, string correlationId, string exportTaskId);
3333

3434
[LoggerMessage(EventId = 503, Level = LogLevel.Debug, Message = "Downloading {file}.")]
3535
public static partial void DownloadingFile(this ILogger logger, string file);
@@ -49,8 +49,8 @@ public static partial class Log
4949
[LoggerMessage(EventId = 508, Level = LogLevel.Error, Message = "Error acknowledging message. Waiting {timeSpan} before next retry. Retry attempt {retryCount}.")]
5050
public static partial void ErrorAcknowledgingMessageWithRetry(this ILogger logger, Exception ex, TimeSpan timeSpan, int retryCount);
5151

52-
[LoggerMessage(EventId = 509, Level = LogLevel.Information, Message = "Sending acknowledgement.")]
53-
public static partial void SendingAckowledgement(this ILogger logger);
52+
[LoggerMessage(EventId = 509, Level = LogLevel.Information, Message = "Sending acknowledgment.")]
53+
public static partial void SendingAcknowledgement(this ILogger logger);
5454

5555
[LoggerMessage(EventId = 510, Level = LogLevel.Error, Message = "Error publishing message. Waiting {timeSpan} before next retry. Retry attempt {retryCount}.")]
5656
public static partial void ErrorPublishingExportCompleteEventWithRetry(this ILogger logger, Exception ex, TimeSpan timeSpan, int retryCount);
@@ -109,13 +109,18 @@ public static partial class Log
109109
[LoggerMessage(EventId = 528, Level = LogLevel.Error, Message = "Failed to export with error {status}.")]
110110
public static partial void DimseExportInstanceError(this ILogger logger, DicomStatus status);
111111

112-
[LoggerMessage(EventId = 529, Level = LogLevel.Error, Message = "Error while adding DICOM C-STORE request: {message}")]
112+
[LoggerMessage(EventId = 529, Level = LogLevel.Error, Message = "{message}")]
113113
public static partial void DimseExportErrorAddingInstance(this ILogger logger, string message, Exception ex);
114114

115115
[LoggerMessage(EventId = 530, Level = LogLevel.Error, Message = "{message}")]
116116
public static partial void ExportException(this ILogger logger, string message, Exception ex);
117117

118118
[LoggerMessage(EventId = 531, Level = LogLevel.Warning, Message = "Export service paused due to insufficient storage space. Available storage space: {availableFreeSpace:D}")]
119119
public static partial void ExportServiceStoppedDueToLowStorageSpace(this ILogger logger, long availableFreeSpace);
120+
121+
[LoggerMessage(EventId = 532, Level = LogLevel.Information, Message = "Correlation ID={correlationId}. Export request {exportTaskId} received & queued for processing.")]
122+
public static partial void ExportRequestQueuedForProcessing(this ILogger logger, string correlationId, string exportTaskId);
123+
124+
120125
}
121126
}

src/InformaticsGateway/Monai.Deploy.InformaticsGateway.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!--
1+
<!--
22
~ Copyright 2022 MONAI Consortium
33
~
44
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -48,7 +48,7 @@
4848
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
4949
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
5050
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
51-
<PackageReference Include="Monai.Deploy.Messaging.RabbitMQ" Version="0.1.6" />
51+
<PackageReference Include="Monai.Deploy.Messaging.RabbitMQ" Version="0.1.7-rc0009" />
5252
<PackageReference Include="Monai.Deploy.Storage" Version="0.2.7" />
5353
<PackageReference Include="Monai.Deploy.Storage.MinIO" Version="0.2.7" />
5454
<PackageReference Include="Polly" Version="7.2.3" />

src/InformaticsGateway/Services/Export/DicomWebExportService.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
using Monai.Deploy.InformaticsGateway.Logging;
3636
using Monai.Deploy.InformaticsGateway.Repositories;
3737
using Monai.Deploy.InformaticsGateway.Services.Common;
38+
using Monai.Deploy.Messaging.Events;
3839
using Polly;
3940

4041
namespace Monai.Deploy.InformaticsGateway.Services.Export
@@ -74,7 +75,7 @@ public DicomWebExportService(
7475

7576
protected override async Task<ExportRequestDataMessage> ExportDataBlockCallback(ExportRequestDataMessage exportRequestData, CancellationToken cancellationToken)
7677
{
77-
using var loggerScope = _logger.BeginScope(new LoggingDataDictionary<string, object> { { "ExportTaskId", exportRequestData.ExportTaskId }, { "CorrelationId", exportRequestData.CorrelationId } });
78+
using var loggerScope = _logger.BeginScope(new LoggingDataDictionary<string, object> { { "ExportTaskId", exportRequestData.ExportTaskId }, { "CorrelationId", exportRequestData.CorrelationId }, { "Filename", exportRequestData.Filename } });
7879

7980
using var scope = _serviceScopeFactory.CreateScope();
8081
var repository = scope.ServiceProvider.GetRequiredService<IInferenceRequestRepository>();
@@ -98,7 +99,7 @@ private async Task HandleTransaction(ExportRequestDataMessage exportRequestData,
9899
{
99100
var errorMessage = $"The specified inference request '{transaction}' cannot be found and will not be exported.";
100101
_logger.InferenceRequestExportDestinationNotFound(transaction);
101-
exportRequestData.SetFailed(errorMessage);
102+
exportRequestData.SetFailed(FileExportStatus.ConfigurationError, errorMessage);
102103
return;
103104
}
104105

@@ -108,7 +109,7 @@ private async Task HandleTransaction(ExportRequestDataMessage exportRequestData,
108109
{
109110
var errorMessage = "The inference request '{transaction}' contains no `outputResources` nor any DICOMweb export destinations.";
110111
_logger.InferenceRequestExportNoDestinationNotFound();
111-
exportRequestData.SetFailed(errorMessage);
112+
exportRequestData.SetFailed(FileExportStatus.ConfigurationError, errorMessage);
112113
return;
113114
}
114115

@@ -126,9 +127,21 @@ private async Task HandleTransaction(ExportRequestDataMessage exportRequestData,
126127

127128
private async Task ExportToDicomWebDestination(IDicomWebClient dicomWebClient, ExportRequestDataMessage exportRequestData, RequestOutputDataResource destination, CancellationToken cancellationToken)
128129
{
130+
DicomFile dicomFile;
131+
try
132+
{
133+
dicomFile = _dicomToolkit.Load(exportRequestData.FileContent);
134+
}
135+
catch (Exception ex)
136+
{
137+
var errorMessage = $"Error reading DICOM file: {ex.Message}.";
138+
_logger.ExportException(errorMessage, ex);
139+
exportRequestData.SetFailed(FileExportStatus.UnsupportedDataType, errorMessage);
140+
return;
141+
}
142+
129143
try
130144
{
131-
var dicomFile = _dicomToolkit.Load(exportRequestData.FileContent);
132145
await Policy
133146
.Handle<Exception>()
134147
.WaitAndRetryAsync(
@@ -147,7 +160,7 @@ await Policy
147160
{
148161
var errorMessage = ex.Message;
149162
_logger.ExportException(errorMessage, ex);
150-
exportRequestData.SetFailed(errorMessage);
163+
exportRequestData.SetFailed(FileExportStatus.ServiceError, errorMessage);
151164
}
152165
}
153166

src/InformaticsGateway/Services/Export/ExportRequestDataMessage.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class ExportRequestDataMessage
2828
public byte[] FileContent { get; private set; }
2929
public bool IsFailed { get; private set; }
3030
public IList<string> Messages { get; init; }
31+
public FileExportStatus ExportStatus { get; private set; }
32+
public string Filename { get; }
3133

3234
public string ExportTaskId
3335
{
@@ -44,15 +46,14 @@ public string[] Destinations
4446
get { return _exportRequest.Destinations; }
4547
}
4648

47-
public string Filename { get; }
48-
4949
public ExportRequestDataMessage(ExportRequestEvent exportRequest, string filename)
5050
{
5151
IsFailed = false;
5252
Messages = new List<string>();
5353

5454
_exportRequest = exportRequest ?? throw new System.ArgumentNullException(nameof(exportRequest));
5555
Filename = filename ?? throw new System.ArgumentNullException(nameof(filename));
56+
ExportStatus = FileExportStatus.Success;
5657
}
5758

5859
public void SetData(byte[] data)
@@ -61,9 +62,11 @@ public void SetData(byte[] data)
6162
FileContent = data;
6263
}
6364

64-
public void SetFailed(string errorMessage)
65+
public void SetFailed(FileExportStatus fileExportStatus, string errorMessage)
6566
{
6667
Guard.Against.NullOrWhiteSpace(errorMessage, nameof(errorMessage));
68+
69+
ExportStatus = fileExportStatus;
6770
IsFailed = true;
6871
Messages.Add(errorMessage);
6972
}

src/InformaticsGateway/Services/Export/ExportRequestEventDetails.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ public ExportRequestEventDetails(ExportRequestEvent exportRequest)
5050
/// <summary>
5151
/// Gets whether the export task is completed or not based on file count.
5252
/// </summary>
53-
public bool IsCompleted
54-
{ get { return (SucceededFiles + FailedFiles) == Files.Count(); } }
53+
public bool IsCompleted { get { return (SucceededFiles + FailedFiles) == Files.Count(); } }
54+
55+
public Dictionary<string, FileExportStatus> FileStatuses { get; private set; } = new Dictionary<string, FileExportStatus>();
56+
5557

5658
public ExportStatus Status
5759
{

src/InformaticsGateway/Services/Export/ExportServiceBase.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ private void OnMessageReceivedCallback(MessageReceivedEventArgs eventArgs)
163163
var exportRequest = eventArgs.Message.ConvertTo<ExportRequestEvent>();
164164
if (_exportRequests.ContainsKey(exportRequest.ExportTaskId))
165165
{
166-
_logger.ExportRequestAlreadyQueued(exportRequest.ExportTaskId);
166+
_logger.ExportRequestAlreadyQueued(exportRequest.CorrelationId, exportRequest.ExportTaskId);
167167
return;
168168
}
169169

@@ -174,6 +174,7 @@ private void OnMessageReceivedCallback(MessageReceivedEventArgs eventArgs)
174174

175175
_exportRequests.Add(exportRequest.ExportTaskId, exportRequestWithDetails);
176176
exportFlow.Post(exportRequestWithDetails);
177+
_logger.ExportRequestQueuedForProcessing(exportRequest.CorrelationId, exportRequest.ExportTaskId);
177178
}
178179

179180
exportFlow.Complete();
@@ -229,7 +230,7 @@ private IEnumerable<ExportRequestDataMessage> DownloadPayloadActionCallback(Expo
229230
{
230231
var errorMessage = $"Error downloading payload.";
231232
_logger.ErrorDownloadingPayload(ex);
232-
exportRequestData.SetFailed(errorMessage);
233+
exportRequestData.SetFailed(FileExportStatus.DownloadError, errorMessage);
233234
}
234235

235236
yield return exportRequestData;
@@ -243,6 +244,7 @@ private void ReportingActionBlock(ExportRequestDataMessage exportRequestData)
243244
var exportRequest = _exportRequests[exportRequestData.ExportTaskId];
244245
lock (SyncRoot)
245246
{
247+
exportRequest.FileStatuses.Add(exportRequestData.Filename, exportRequestData.ExportStatus);
246248
if (exportRequestData.IsFailed)
247249
{
248250
exportRequest.FailedFiles++;
@@ -265,7 +267,8 @@ private void ReportingActionBlock(ExportRequestDataMessage exportRequestData)
265267

266268
_logger.ExportCompleted(exportRequest.FailedFiles, exportRequest.Files.Count());
267269

268-
var exportCompleteEvent = new ExportCompleteEvent(exportRequest, exportRequest.Status);
270+
var exportCompleteEvent = new ExportCompleteEvent(exportRequest, exportRequest.Status, exportRequest.FileStatuses);
271+
269272
var jsonMessage = new JsonMessage<ExportCompleteEvent>(exportCompleteEvent, MessageBrokerConfiguration.InformaticsGatewayApplicationId, exportRequest.CorrelationId, exportRequest.DeliveryTag);
270273

271274
Policy
@@ -278,7 +281,7 @@ private void ReportingActionBlock(ExportRequestDataMessage exportRequestData)
278281
})
279282
.Execute(() =>
280283
{
281-
_logger.SendingAckowledgement();
284+
_logger.SendingAcknowledgement();
282285
_messageSubscriber.Acknowledge(jsonMessage);
283286
});
284287

src/InformaticsGateway/Services/Export/ScuExportService.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Threading;
2121
using System.Threading.Tasks;
2222
using Ardalis.GuardClauses;
23+
using FellowOakDicom;
2324
using FellowOakDicom.Network;
2425
using FellowOakDicom.Network.Client;
2526
using Microsoft.Extensions.DependencyInjection;
@@ -30,6 +31,7 @@
3031
using Monai.Deploy.InformaticsGateway.Configuration;
3132
using Monai.Deploy.InformaticsGateway.Logging;
3233
using Monai.Deploy.InformaticsGateway.Repositories;
34+
using Monai.Deploy.Messaging.Events;
3335
using Polly;
3436

3537
namespace Monai.Deploy.InformaticsGateway.Services.Export
@@ -86,7 +88,7 @@ private async Task HandleDesination(ExportRequestDataMessage exportRequestData,
8688
catch (ConfigurationException ex)
8789
{
8890
_logger.ScuExportConfigurationError(ex.Message, ex);
89-
exportRequestData.SetFailed(ex.Message);
91+
exportRequestData.SetFailed(FileExportStatus.ConfigurationError, ex.Message);
9092
return;
9193
}
9294

@@ -155,10 +157,21 @@ private async Task<bool> GenerateRequestsAsync(
155157
IDicomClient client,
156158
ManualResetEvent manualResetEvent)
157159
{
160+
DicomFile dicomFile;
158161
try
159162
{
160-
var dicomFile = _dicomToolkit.Load(exportRequestData.FileContent);
163+
dicomFile = _dicomToolkit.Load(exportRequestData.FileContent);
164+
}
165+
catch (Exception ex)
166+
{
167+
var errorMessage = $"Error reading DICOM file: {ex.Message}";
168+
_logger.ExportException(errorMessage, ex);
169+
exportRequestData.SetFailed(FileExportStatus.UnsupportedDataType, errorMessage);
170+
return false;
171+
}
161172

173+
try
174+
{
162175
var request = new DicomCStoreRequest(dicomFile);
163176

164177
request.OnResponseReceived += (req, response) =>
@@ -171,7 +184,7 @@ private async Task<bool> GenerateRequestsAsync(
171184
{
172185
var errorMessage = $"Failed to export with error {response.Status}";
173186
_logger.DimseExportInstanceError(response.Status);
174-
exportRequestData.SetFailed(errorMessage);
187+
exportRequestData.SetFailed(FileExportStatus.ServiceError, errorMessage);
175188
}
176189
manualResetEvent.Set();
177190
};
@@ -183,7 +196,7 @@ private async Task<bool> GenerateRequestsAsync(
183196
{
184197
var errorMessage = $"Error while adding DICOM C-STORE request: {exception.Message}";
185198
_logger.DimseExportErrorAddingInstance(exception.Message, exception);
186-
exportRequestData.SetFailed(errorMessage);
199+
exportRequestData.SetFailed(FileExportStatus.ServiceError, errorMessage);
187200
return false;
188201
}
189202
}
@@ -213,7 +226,7 @@ private void HandleCStoreException(Exception ex, ExportRequestDataMessage export
213226
}
214227

215228
_logger.ExportException(errorMessage, ex);
216-
exportRequestData.SetFailed(errorMessage);
229+
exportRequestData.SetFailed(FileExportStatus.ServiceError, errorMessage);
217230
}
218231
}
219232
}

0 commit comments

Comments
 (0)