This is an automated email from the ASF dual-hosted git repository. ghenzler pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/felix-dev.git
commit 8ecf2064bf9f51cfea6bbabadbc0b769b1432978 Author: georg.henzler <[email protected]> AuthorDate: Tue Dec 14 21:28:08 2021 +0100 FELIX-6480 Make logging of HC monitor more flexible --- healthcheck/README.md | 4 +- .../hc/core/impl/monitor/HealthCheckMonitor.java | 92 ++++----- .../felix/hc/core/impl/monitor/HealthState.java | 20 +- .../impl/servlet/ResultTxtVerboseSerializer.java | 28 ++- .../core/impl/monitor/HealthCheckMonitorTest.java | 207 ++++++++++++++++++++- 5 files changed, 292 insertions(+), 59 deletions(-) diff --git a/healthcheck/README.md b/healthcheck/README.md index 2007d5f..b40505b 100644 --- a/healthcheck/README.md +++ b/healthcheck/README.md @@ -274,8 +274,8 @@ Property | Type | Default | Description `registerHealthyMarkerService` | boolean | true | For the case a given tag/name is healthy, will register a service `org.apache.felix.hc.api.condition.Healthy` with property tag=<tagname> (or name=<hc.name>) that other services can depend on. For the special case of the tag `systemready`, the marker service `org.apache.felix.hc.api.condition.SystemReady` is registered `registerUnhealthyMarkerService` | boolean | false | For the case a given tag/name is **un**healthy, will register a service `org.apache.felix.hc.api.condition.Unhealthy` with property tag=<tagname> (or name=<hc.name>) that other services can depend on `treatWarnAsHealthy` | boolean | true | `WARN` usually means [the system is usable](#semantic-meaning-of-health-check-results), hence WARN is treated as healthy by default. When set to false `WARN` is treated as `Unhealthy` -`sendEvents` | enum `NONE`, `STATUS_CHANGES` or `ALL` | `STATUS_CHANGES` | Whether to send events for health check status changes. See [below](#osgi-events-for-health-check-status-changes) for details. -`logResults` | enum `NONE`, `STATUS_CHANGES` or `ALL` | `NONE ` | Whether to log the result of the monitor to the regular log file +`sendEvents` | enum `NONE`, `STATUS_CHANGES`, `STATUS_CHANGES_OR_NOT_OK` or `ALL` | `STATUS_CHANGES` | Whether to send events for health check status changes. See [below](#osgi-events-for-health-check-status-changes) for details. +`logResults` | enum `NONE`, `STATUS_CHANGES`, `STATUS_CHANGES_OR_NOT_OK` or `ALL` | `NONE ` | Whether to log the result of the monitor to the regular log file `isDynamic` | boolean | false | In dynamic mode all checks for names/tags are monitored individually (this means events are sent/services registered for name only, never for given tags). This mode allows to use `*` in tags to query for all health checks in system. It is also possible to query for all except certain tags by using `-`, e.g. by configuring the values `*`, `-tag1` and `-tag2` for `tags`. ### Marker Service to depend on a health status in SCR Components diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java index 6da64ed..17bd1d8 100644 --- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java +++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java @@ -26,7 +26,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.felix.hc.api.HealthCheck; -import org.apache.felix.hc.api.Result.Status; import org.apache.felix.hc.api.condition.Healthy; import org.apache.felix.hc.api.condition.SystemReady; import org.apache.felix.hc.api.condition.Unhealthy; @@ -78,22 +77,22 @@ public class HealthCheckMonitor implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(HealthCheckMonitor.class); public enum ChangeType { - NONE, STATUS_CHANGES, ALL + NONE, STATUS_CHANGES, STATUS_CHANGES_OR_NOT_OK, ALL } @ObjectClassDefinition(name = "Health Check Monitor", description = "Regularly executes health checks according to given interval/cron expression") public @interface Config { - @AttributeDefinition(name = "Tags", description = "List of tags to query regularly") + @AttributeDefinition(name = "Tags", description = "List of tags to monitor") String[] tags() default {}; - @AttributeDefinition(name = "Names", description = "List of health check names to query regularly") + @AttributeDefinition(name = "Names", description = "List of health check names to monitor") String[] names() default {}; - @AttributeDefinition(name = "Interval (Sec)", description = "Will execute the checks for give tags every n seconds (either use intervalInSec or cronExpression )") + @AttributeDefinition(name = "Interval (Sec)", description = "Will execute the checks for given tags/names every n seconds (either use intervalInSec or cronExpression )") long intervalInSec() default 0; - @AttributeDefinition(name = "Interval (Cron Expresson)", description = "Will execute the checks for give tags according to cron expression") + @AttributeDefinition(name = "Interval (Cron Expresson)", description = "Will execute the checks for given tags/names according to cron expression") String cronExpression() default ""; @AttributeDefinition(name = "Register Healthy Marker Service", description = "For the case a given tag/name is healthy, will register a service Healthy with property tag=<tagname> (or name=<hc.name>) that other services can depend on") @@ -102,16 +101,16 @@ public class HealthCheckMonitor implements Runnable { @AttributeDefinition(name = "Register Unhealthy Marker Service", description = "For the case a given tag/name is unhealthy, will register a service Unhealthy with property tag=<tagname> (or name=<hc.name>) that other services can depend on") boolean registerUnhealthyMarkerService() default false; - @AttributeDefinition(name = "Treat WARN as Healthy", description = "Whether to treat status WARN as healthy (it normally should because WARN indicates a working system that only possibly might become unavailable if no action is taken") + @AttributeDefinition(name = "Treat WARN as Healthy", description = "Whether to treat status WARN as healthy (defaults to true because WARN indicates a working system that only possibly might become unavailable if no action is taken)") boolean treatWarnAsHealthy() default true; - @AttributeDefinition(name = "Send Events", description = "Send OSGi events for the case a status has changed or for all executions or for none.") + @AttributeDefinition(name = "Send Events", description = "What updates should be sent as OSGi events (none, status changes, status changes and not ok results, all updates)") ChangeType sendEvents() default ChangeType.STATUS_CHANGES; - @AttributeDefinition(name = "Log results", description = "Log the result to the regular log file.") + @AttributeDefinition(name = "Log results", description = "What updates should be logged to regular log file (none, status changes, status changes and not ok results, all updates)") ChangeType logResults() default ChangeType.NONE; - @AttributeDefinition(name = "Dynamic Mode", description = "In dynamic mode all checks for names/tags are monitored individually (this means events are sent/services registered for name only, never for given tags). This mode allows to use '*' in tags to query for all health checks in system. It is also possible to query for all except certain tags by using '-', e.g. by configuring the values '*', '-tag1' and '-tag2' for tags.") + @AttributeDefinition(name = "Resolve Tags (dynamic)", description = "In dynamic mode tags are resolved to a list of health checks that are monitored individually (this means events are sent/services are registered for name only, never for given tags). This mode allows to use '*' in tags to query for all health checks in system. It is also possible to query for all except certain tags by using '-', e.g. by configuring the values '*', '-tag1' and '-tag2' for tags.") boolean isDynamic() default false; @AttributeDefinition @@ -166,7 +165,7 @@ public class HealthCheckMonitor implements Runnable { this.names = Arrays.stream(config.names()).filter(StringUtils::isNotBlank).collect(toList()); this.isDynamic = config.isDynamic(); initHealthStates(); - + this.registerHealthyMarkerService = config.registerHealthyMarkerService(); this.registerUnhealthyMarkerService = config.registerUnhealthyMarkerService(); @@ -263,12 +262,10 @@ public class HealthCheckMonitor implements Runnable { healthStates.values().parallelStream().forEach(healthState -> runWithThreadNameContext(healthState::update) ); - if(logResults != ChangeType.NONE) { logResults(); } - LOG.debug("Updated {} health states for tags {} and names {}", healthStates.size(), this.tags, this.names); } catch (Exception e) { @@ -278,42 +275,51 @@ public class HealthCheckMonitor implements Runnable { } private void logResults() { - - List<HealthCheckExecutionResult> executionResults = healthStates.values().stream() - .filter(healthState -> { return healthState.hasChanged() || logResults == ChangeType.ALL; }) - .flatMap( healthState -> { - HealthCheckExecutionResult executionResult = healthState.getExecutionResult(); - List<HealthCheckExecutionResult> execResults; - if (executionResult instanceof CombinedExecutionResult) { - execResults = ((CombinedExecutionResult) executionResult).getExecutionResults(); - } else { - execResults = Arrays.asList(executionResult); - } - return execResults.stream(); - }) - .sorted() - .collect(toList()); - - if(executionResults.isEmpty()) { - return; - } - CombinedExecutionResult combinedResultForLogging = new CombinedExecutionResult(executionResults); - Status hcStatus = combinedResultForLogging.getHealthCheckResult().getStatus(); - if(!LOG.isInfoEnabled() && hcStatus == Status.OK) { - return; + for(HealthState healthState: healthStates.values()) { + + HealthCheckExecutionResult executionResult = healthState.getExecutionResult(); + + boolean isOk = executionResult.getHealthCheckResult().isOk(); + if(!LOG.isInfoEnabled() && isOk) { + return; // with INFO disabled even ChangeType.ALL would not log it + } + boolean changeToBeLogged = healthState.hasChanged() && (logResults == ChangeType.STATUS_CHANGES || logResults == ChangeType.STATUS_CHANGES_OR_NOT_OK); + boolean notOkToBeLogged = !isOk && logResults == ChangeType.STATUS_CHANGES_OR_NOT_OK; + if(!changeToBeLogged && !notOkToBeLogged && logResults != ChangeType.ALL) { + continue; + } + + List<HealthCheckExecutionResult> execResults; + boolean isCombinedResult = executionResult instanceof CombinedExecutionResult; + if (isCombinedResult) { + execResults = ((CombinedExecutionResult) executionResult).getExecutionResults(); + } else { + execResults = Arrays.asList(executionResult); + } + + String label = + isCombinedResult ? String.format("Health State for %s '%s': healthy:%b isOk:%b hasChanged:%b count HCs:%d", (healthState.isTag() ? "tag" : "name"), healthState.getTagOrName(), healthState.isHealthy(), isOk, healthState.hasChanged(), execResults.size()) + : String.format("Health State for '%s': healthy:%b hasChanged:%b", executionResult.getHealthCheckMetadata().getTitle(), healthState.isHealthy(), healthState.hasChanged()); + if(!healthState.hasChanged() && notOkToBeLogged) { + // filter the ok items to not clutter the log file + execResults = execResults.stream().filter(r -> !r.getHealthCheckResult().isOk()).collect(toList()); + } + + String logMsg = resultTxtVerboseSerializer.serialize(label, execResults, false); + logResultItem(isOk, logMsg); } - String logMsg = resultTxtVerboseSerializer.serialize(combinedResultForLogging.getHealthCheckResult(), combinedResultForLogging.getExecutionResults(), false); - String firstLineMsg = (logResults == ChangeType.STATUS_CHANGES) ? "Status Changes:" : ""; - if(hcStatus == Status.OK) { - LOG.info(firstLineMsg+"\n"+logMsg); + } + + void logResultItem(boolean isOk, String msg) { + if(isOk) { + LOG.info(msg); } else { - LOG.warn(firstLineMsg+"\n"+logMsg); + LOG.warn(msg); } - } - + private void runWithThreadNameContext(Runnable r) { String threadNameToRestore = Thread.currentThread().getName(); try { diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java index ed5d33f..3f2c345 100644 --- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java +++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java @@ -25,11 +25,9 @@ import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; -import java.util.function.Function; import org.apache.felix.hc.api.HealthCheck; import org.apache.felix.hc.api.Result; -import org.apache.felix.hc.api.Result.Status; import org.apache.felix.hc.api.condition.Healthy; import org.apache.felix.hc.api.condition.SystemReady; import org.apache.felix.hc.api.condition.Unhealthy; @@ -109,6 +107,18 @@ class HealthState { return statusChanged; } + public boolean isHealthy() { + return isHealthy; + } + + String getTagOrName() { + return tagOrName; + } + + boolean isTag() { + return isTag; + } + HealthCheckExecutionResult getExecutionResult() { return executionResult; } @@ -214,8 +224,10 @@ class HealthState { private void sendEvents(HealthCheckExecutionResult executionResult, Result.Status previousStatus) { ChangeType sendEventsConfig = monitor.getSendEvents(); - if ((sendEventsConfig == ChangeType.STATUS_CHANGES && statusChanged) || sendEventsConfig == ChangeType.ALL) { - + if (sendEventsConfig == ChangeType.ALL + || (statusChanged && (sendEventsConfig == ChangeType.STATUS_CHANGES || sendEventsConfig == ChangeType.STATUS_CHANGES_OR_NOT_OK)) + || (!executionResult.getHealthCheckResult().isOk() && sendEventsConfig == ChangeType.STATUS_CHANGES_OR_NOT_OK)) { + String eventSuffix = statusChanged ? EVENT_TOPIC_SUFFIX_STATUS_CHANGED : EVENT_TOPIC_SUFFIX_STATUS_UPDATED; String logMsg = "Posted event for topic '{}': " + (statusChanged ? "Status change from {} to {}" : "Result updated (status {})"); diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java index 1148344..f5bbc8f 100644 --- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java +++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java @@ -62,27 +62,47 @@ public class ResultTxtVerboseSerializer { colWidthLog = totalWidth - colWidthWithoutLog; } - public String serialize(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, boolean includeDebug) { + // without overall result, only the execution results are listed without header + public String serialize(String label, final List<HealthCheckExecutionResult> executionResults, boolean includeDebug) { + StringBuilder resultStr = new StringBuilder(); + + resultStr.append(label + "\n"); + resultStr.append(serializeResults(executionResults, includeDebug)); - LOG.debug("Sending verbose txt response... "); + return resultStr.toString(); + } + + public String serialize(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, boolean includeDebug) { StringBuilder resultStr = new StringBuilder(); resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE); - resultStr.append(center("Overall Health Result: " + overallResult.getStatus().toString(), totalWidth) + NEWLINE); + resultStr.append( + center("Overall Health Result: " + overallResult.getStatus().toString(), totalWidth) + NEWLINE); resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE); + resultStr.append(rightPad("Name", colWidthName)); resultStr.append(rightPad("Result", colWidthResult)); resultStr.append(rightPad("Timing", colWidthTiming)); resultStr.append("Logs" + NEWLINE); resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE); + resultStr.append(serializeResults(executionResults, includeDebug)); + resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE); + + return resultStr.toString(); + + } + + private String serializeResults(final List<HealthCheckExecutionResult> executionResults, boolean includeDebug) { + + StringBuilder resultStr = new StringBuilder(); + final DateFormat dfShort = new SimpleDateFormat("HH:mm:ss.SSS"); for (HealthCheckExecutionResult healthCheckResult : executionResults) { appendVerboseTxtForResult(resultStr, healthCheckResult, includeDebug, dfShort); } - resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE); return resultStr.toString(); diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java index 2d9502d..47f69b5 100644 --- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java +++ b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java @@ -20,7 +20,12 @@ package org.apache.felix.hc.core.impl.monitor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -44,13 +49,18 @@ import org.apache.felix.hc.core.impl.executor.ExecutionResult; import org.apache.felix.hc.core.impl.executor.ExtendedHealthCheckExecutor; import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool; import org.apache.felix.hc.core.impl.scheduling.AsyncIntervalJob; +import org.apache.felix.hc.core.impl.servlet.ResultTxtVerboseSerializer; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; @@ -60,12 +70,15 @@ import org.osgi.service.component.ComponentContext; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; +@RunWith(MockitoJUnitRunner.class) public class HealthCheckMonitorTest { private static final String TEST_TAG = "test-tag"; + private static final String HC_RESULT_SERIALIZED = "HC result serialized"; + @Spy @InjectMocks - private HealthCheckMonitor healthCheckMonitor = new HealthCheckMonitor(); + private HealthCheckMonitor healthCheckMonitor; @Mock private BundleContext bundleContext; @@ -103,9 +116,11 @@ public class HealthCheckMonitorTest { @Mock private ServiceRegistration<Unhealthy> unhealthyRegistration; + @Mock + private ResultTxtVerboseSerializer resultTxtVerboseSerializer; + @Before public void before() throws ReflectiveOperationException { - MockitoAnnotations.initMocks(this); for (Method m : HealthCheckMonitor.Config.class.getDeclaredMethods()) { when(m.invoke(config)).thenReturn(m.getDefaultValue()); @@ -115,10 +130,12 @@ public class HealthCheckMonitorTest { when(config.tags()).thenReturn(new String[] { TEST_TAG }); when(healthCheckMetadata.getServiceReference()).thenReturn(healthCheckServiceRef); - + when(healthCheckMetadata.getTitle()).thenReturn("Test Check"); + Dictionary<String,Object> componentProps = new Hashtable<>(); componentProps.put(ComponentConstants.COMPONENT_ID, 7L); when(componentContext.getProperties()).thenReturn(componentProps); + } @Test @@ -188,9 +205,9 @@ public class HealthCheckMonitorTest { private void resetMarkerServicesContext() { reset(bundleContext, healthyRegistration, unhealthyRegistration); when(bundleContext.registerService(eq(Healthy.class), eq(HealthState.MARKER_SERVICE_HEALTHY), any())).thenReturn((ServiceRegistration<Healthy>) healthyRegistration); - when(bundleContext.registerService(eq(Unhealthy.class), eq(HealthState.MARKER_SERVICE_UNHEALTHY), any())).thenReturn(unhealthyRegistration); + lenient().when(bundleContext.registerService(eq(Unhealthy.class), eq(HealthState.MARKER_SERVICE_UNHEALTHY), any())).thenReturn(unhealthyRegistration); } - + @Test public void testRunSendEventsStatusChanges() throws InvalidSyntaxException { @@ -271,7 +288,185 @@ public class HealthCheckMonitorTest { assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthState.EVENT_PROP_PREVIOUS_STATUS)); assertEquals("org/apache/felix/health/component/org/apache/felix/TestHealthCheck/UPDATED", postedEvents.get(1).getTopic()); } + + + + @Test + public void testRunLogAll() throws InvalidSyntaxException { + + prepareLoggingTest(HealthCheckMonitor.ChangeType.ALL); + + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:false .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.WARN); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.WARN); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:true hasChanged:false .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.CRITICAL); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.CRITICAL); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:false hasChanged:false .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:false .*" + HC_RESULT_SERIALIZED)); + + } + + + + @Test + public void testRunLogStatusChanges() throws InvalidSyntaxException { + + prepareLoggingTest(HealthCheckMonitor.ChangeType.STATUS_CHANGES); + + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), anyString()); + + reset(healthCheckMonitor); + setHcResult(Result.Status.WARN); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.WARN); + healthCheckMonitor.run(); + verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), anyString()); + + reset(healthCheckMonitor); + setHcResult(Result.Status.CRITICAL); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.CRITICAL); + healthCheckMonitor.run(); + verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), anyString()); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), anyString()); + + } + + + @Test + public void testRunLogStatusChangesOrNotOk() throws InvalidSyntaxException { + + prepareLoggingTest(HealthCheckMonitor.ChangeType.STATUS_CHANGES_OR_NOT_OK); + + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), anyString()); + + reset(healthCheckMonitor); + setHcResult(Result.Status.WARN); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.WARN); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:true hasChanged:false .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.CRITICAL); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.CRITICAL); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(false), matches(".*healthy:false hasChanged:false .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor).logResultItem(eq(true), matches(".*healthy:true hasChanged:true .*" + HC_RESULT_SERIALIZED)); + + reset(healthCheckMonitor); + setHcResult(Result.Status.OK); + healthCheckMonitor.run(); + verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), anyString()); + + } + + + @Test + public void testRunLogStatusNone() throws InvalidSyntaxException { + + prepareLoggingTest(HealthCheckMonitor.ChangeType.NONE); + + for (Result.Status status : Arrays.asList( + Result.Status.OK, Result.Status.OK, + Result.Status.WARN, Result.Status.WARN, + Result.Status.CRITICAL, Result.Status.CRITICAL, + Result.Status.OK, Result.Status.OK)) { + + setHcResult(status); + healthCheckMonitor.run(); + } + + // ensure logging is never called for whatever state remains the same or changes + verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), anyString()); + + } + private void prepareLoggingTest(HealthCheckMonitor.ChangeType loggingChangeType) throws InvalidSyntaxException { + when(config.sendEvents()).thenReturn(HealthCheckMonitor.ChangeType.NONE); + when(config.logResults()).thenReturn(loggingChangeType); + healthCheckMonitor.activate(bundleContext, config, componentContext); + healthCheckMonitor.healthStates.put(TEST_TAG, new HealthState(healthCheckMonitor, TEST_TAG, true)); + + when(resultTxtVerboseSerializer.serialize(any(String.class), anyList(), eq(false))).thenAnswer(new Answer<String>() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + return (String) args[0] + " " + HC_RESULT_SERIALIZED; + } + }); + } private void setHcResult(Result.Status status) { when(healthCheckExecutor.execute(HealthCheckSelector.tags(TEST_TAG)))
