You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gh...@apache.org on 2021/12/14 20:36:19 UTC
[felix-dev] 01/02: FELIX-6480 Make logging of HC monitor more flexible
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 <ge...@netcentric.biz>
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)))