adding additional logging to resource modified submitter svc (#5370)

* adding additional logging to resource modified submitter svc

* s[otlestt

* review fixes

* spotless

* fixing test

---------

Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-mbp.home>
This commit is contained in:
TipzCM 2023-10-20 09:56:32 -04:00 committed by GitHub
parent 20c8109a32
commit af1d46fb04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 132 additions and 11 deletions

View File

@ -145,8 +145,8 @@ public class ResourceModifiedSubmitterSvc implements IResourceModifiedConsumer,
return theStatus -> { return theStatus -> {
boolean processed = true; boolean processed = true;
ResourceModifiedMessage resourceModifiedMessage = null; ResourceModifiedMessage resourceModifiedMessage = null;
try {
try {
// delete the entry to lock the row to ensure unique processing // delete the entry to lock the row to ensure unique processing
boolean wasDeleted = deletePersistedResourceModifiedMessage( boolean wasDeleted = deletePersistedResourceModifiedMessage(
thePersistedResourceModifiedMessage.getPersistedResourceModifiedMessagePk()); thePersistedResourceModifiedMessage.getPersistedResourceModifiedMessagePk());
@ -159,15 +159,27 @@ public class ResourceModifiedSubmitterSvc implements IResourceModifiedConsumer,
resourceModifiedMessage = optionalResourceModifiedMessage.get(); resourceModifiedMessage = optionalResourceModifiedMessage.get();
submitResourceModified(resourceModifiedMessage); submitResourceModified(resourceModifiedMessage);
} }
} catch (MessageDeliveryException exception) { } catch (MessageDeliveryException exception) {
// we encountered an issue when trying to send the message so mark the transaction for rollback // we encountered an issue when trying to send the message so mark the transaction for rollback
String payloadId = "[unknown]";
String subscriptionId = "[unknown]";
if (resourceModifiedMessage != null) {
payloadId = resourceModifiedMessage.getPayloadId();
subscriptionId = resourceModifiedMessage.getSubscriptionId();
}
ourLog.error( ourLog.error(
"Channel submission failed for resource with id {} matching subscription with id {}. Further attempts will be performed at later time.", "Channel submission failed for resource with id {} matching subscription with id {}. Further attempts will be performed at later time.",
resourceModifiedMessage.getPayloadId(), payloadId,
resourceModifiedMessage.getSubscriptionId()); subscriptionId,
exception);
processed = false; processed = false;
theStatus.setRollbackOnly(); theStatus.setRollbackOnly();
} catch (Exception ex) {
// catch other errors
ourLog.error(
"Unexpected error encountered while processing resource modified message. Marking as processed to prevent further errors.",
ex);
processed = true;
} }
return processed; return processed;
@ -179,7 +191,6 @@ public class ResourceModifiedSubmitterSvc implements IResourceModifiedConsumer,
ResourceModifiedMessage resourceModifiedMessage = null; ResourceModifiedMessage resourceModifiedMessage = null;
try { try {
resourceModifiedMessage = myResourceModifiedMessagePersistenceSvc.inflatePersistedResourceModifiedMessage( resourceModifiedMessage = myResourceModifiedMessagePersistenceSvc.inflatePersistedResourceModifiedMessage(
thePersistedResourceModifiedMessage); thePersistedResourceModifiedMessage);
@ -193,14 +204,17 @@ public class ResourceModifiedSubmitterSvc implements IResourceModifiedConsumer,
persistedResourceModifiedMessagePk.getResourceVersion()); persistedResourceModifiedMessagePk.getResourceVersion());
ourLog.warn( ourLog.warn(
"Scheduled submission will be ignored since resource {} cannot be found", idType.asStringValue()); "Scheduled submission will be ignored since resource {} cannot be found",
idType.asStringValue(),
e);
} catch (Exception ex) {
ourLog.error("Unknown error encountered on inflation of resources.", ex);
} }
return Optional.ofNullable(resourceModifiedMessage); return Optional.ofNullable(resourceModifiedMessage);
} }
private boolean deletePersistedResourceModifiedMessage(IPersistedResourceModifiedMessagePK theResourceModifiedPK) { private boolean deletePersistedResourceModifiedMessage(IPersistedResourceModifiedMessagePK theResourceModifiedPK) {
try { try {
// delete the entry to lock the row to ensure unique processing // delete the entry to lock the row to ensure unique processing
return myResourceModifiedMessagePersistenceSvc.deleteByPK(theResourceModifiedPK); return myResourceModifiedMessagePersistenceSvc.deleteByPK(theResourceModifiedPK);
@ -213,6 +227,9 @@ public class ResourceModifiedSubmitterSvc implements IResourceModifiedConsumer,
// the message // the message
// successfully before we did. // successfully before we did.
return false;
} catch (Exception ex) {
ourLog.error("Unknown exception when deleting persisted resource modified message. Returning false.", ex);
return false; return false;
} }
} }

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.subscription.svc; package ca.uhn.fhir.jpa.subscription.svc;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService; import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
import ca.uhn.fhir.jpa.model.entity.PersistedResourceModifiedMessageEntityPK;
import ca.uhn.fhir.jpa.model.entity.ResourceModifiedEntity; import ca.uhn.fhir.jpa.model.entity.ResourceModifiedEntity;
import ca.uhn.fhir.jpa.model.entity.StorageSettings; import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings; import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings;
@ -9,7 +10,12 @@ import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFact
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
import ca.uhn.fhir.jpa.subscription.submit.svc.ResourceModifiedSubmitterSvc; import ca.uhn.fhir.jpa.subscription.submit.svc.ResourceModifiedSubmitterSvc;
import ca.uhn.fhir.jpa.svc.MockHapiTransactionService; import ca.uhn.fhir.jpa.svc.MockHapiTransactionService;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.subscription.api.IResourceModifiedMessagePersistenceSvc; import ca.uhn.fhir.subscription.api.IResourceModifiedMessagePersistenceSvc;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -19,15 +25,24 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.MessageDeliveryException;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.SimpleTransactionStatus; import org.springframework.transaction.support.SimpleTransactionStatus;
import java.util.List;
import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -35,6 +50,8 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class ResourceModifiedSubmitterSvcTest { public class ResourceModifiedSubmitterSvcTest {
private final ch.qos.logback.classic.Logger ourLogger = (Logger) LoggerFactory.getLogger(ResourceModifiedSubmitterSvc.class);
@Mock @Mock
StorageSettings myStorageSettings; StorageSettings myStorageSettings;
@Mock @Mock
@ -46,6 +63,9 @@ public class ResourceModifiedSubmitterSvcTest {
@Mock @Mock
IChannelProducer myChannelProducer; IChannelProducer myChannelProducer;
@Mock
ListAppender<ILoggingEvent> myListAppender;
ResourceModifiedSubmitterSvc myResourceModifiedSubmitterSvc; ResourceModifiedSubmitterSvc myResourceModifiedSubmitterSvc;
TransactionStatus myCapturingTransactionStatus; TransactionStatus myCapturingTransactionStatus;
@ -81,7 +101,7 @@ public class ResourceModifiedSubmitterSvcTest {
} }
@Test @Test
public void testSubmitPersisedResourceModifiedMessage_withExistingPersistedResourceModifiedMessage_willSucceed(){ public void testSubmitPersistedResourceModifiedMessage_withExistingPersistedResourceModifiedMessage_willSucceed(){
// given // given
// a successful deletion implies that the message did exist. // a successful deletion implies that the message did exist.
when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any())).thenReturn(true); when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any())).thenReturn(true);
@ -94,11 +114,95 @@ public class ResourceModifiedSubmitterSvcTest {
assertThat(wasProcessed, is(Boolean.TRUE)); assertThat(wasProcessed, is(Boolean.TRUE));
assertThat(myCapturingTransactionStatus.isRollbackOnly(), is(Boolean.FALSE)); assertThat(myCapturingTransactionStatus.isRollbackOnly(), is(Boolean.FALSE));
verify(myChannelProducer, times(1)).send(any()); verify(myChannelProducer, times(1)).send(any());
} }
@Test @Test
public void testSubmitPersisedResourceModifiedMessage_whenMessageWasAlreadyProcess_willSucceed(){ public void testSubmitPersistedResource_logsDeleteAndInflationExceptions() {
// setup
String deleteExMsg = "Delete Exception";
String inflationExMsg = "Inflation Exception";
String patientId = "/Patient/123/_history/1";
ResourceModifiedEntity resourceModified = new ResourceModifiedEntity();
PersistedResourceModifiedMessageEntityPK rpm = new PersistedResourceModifiedMessageEntityPK();
rpm.setResourcePid(patientId);
rpm.setResourceVersion("1");
resourceModified.setResourceModifiedEntityPK(rpm);
ourLogger.addAppender(myListAppender);
ourLogger.setLevel(Level.ERROR);
// when
when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any()))
.thenThrow(new RuntimeException(deleteExMsg));
when(myResourceModifiedMessagePersistenceSvc.inflatePersistedResourceModifiedMessage(any()))
.thenThrow(new RuntimeException(inflationExMsg));
// test
boolean processed = myResourceModifiedSubmitterSvc.submitPersisedResourceModifiedMessage(resourceModified);
// verify
assertTrue(processed);
ArgumentCaptor<ILoggingEvent> logEvent = ArgumentCaptor.forClass(ILoggingEvent.class);
verify(myListAppender, atLeast(2))
.doAppend(logEvent.capture());
List<ILoggingEvent> logs = logEvent.getAllValues();
boolean hasDeleteException = false;
boolean hasInflationException = false;
for (ILoggingEvent log : logs) {
if (log.getThrowableProxy().getMessage().contains(deleteExMsg)) {
hasDeleteException = true;
}
if (log.getThrowableProxy().getMessage().contains(inflationExMsg)) {
hasInflationException = true;
}
}
assertTrue(hasDeleteException);
assertTrue(hasInflationException);
}
@Test
public void testSubmitPersistedResource_withMissingResource_processes() {
// setup
String patientId = "Patient/123";
String exceptionString = "A random exception";
ResourceModifiedEntity resourceModified = new ResourceModifiedEntity();
PersistedResourceModifiedMessageEntityPK rpm = new PersistedResourceModifiedMessageEntityPK();
rpm.setResourcePid(patientId);
rpm.setResourceVersion("1");
resourceModified.setResourceModifiedEntityPK(rpm);
ResourceModifiedMessage msg = new ResourceModifiedMessage();
ourLogger.addAppender(myListAppender);
ourLogger.setLevel(Level.ERROR);
// when
when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any()))
.thenReturn(true);
when(myResourceModifiedMessagePersistenceSvc.inflatePersistedResourceModifiedMessage(any()))
.thenReturn(msg);
when(myChannelProducer.send(any()))
.thenThrow(new RuntimeException(exceptionString));
// test
boolean processed = myResourceModifiedSubmitterSvc.submitPersisedResourceModifiedMessage(resourceModified);
// then
assertTrue(processed);
// verify
verify(myChannelProducer)
.send(any());
ArgumentCaptor<ILoggingEvent> loggingCaptor = ArgumentCaptor.forClass(ILoggingEvent.class);
verify(myListAppender).doAppend(loggingCaptor.capture());
ILoggingEvent event = loggingCaptor.getValue();
assertNotNull(event);
assertTrue(event.getThrowableProxy().getMessage().contains(exceptionString));
}
@Test
public void testSubmitPersistedResourceModifiedMessage_whenMessageWasAlreadyProcess_willSucceed(){
// given // given
// deletion fails, someone else was faster and processed the message // deletion fails, someone else was faster and processed the message
when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any())).thenReturn(false); when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any())).thenReturn(false);
@ -116,7 +220,7 @@ public class ResourceModifiedSubmitterSvcTest {
} }
@Test @Test
public void testSubmitPersisedResourceModifiedMessage_whitErrorOnSending_willRollbackDeletion(){ public void testSubmitPersistedResourceModifiedMessage_whitErrorOnSending_willRollbackDeletion(){
// given // given
when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any())).thenReturn(true); when(myResourceModifiedMessagePersistenceSvc.deleteByPK(any())).thenReturn(true);
when(myResourceModifiedMessagePersistenceSvc.inflatePersistedResourceModifiedMessage(any())).thenReturn(new ResourceModifiedMessage()); when(myResourceModifiedMessagePersistenceSvc.inflatePersistedResourceModifiedMessage(any())).thenReturn(new ResourceModifiedMessage());