You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/11/29 10:46:21 UTC
[camel] 02/04: CAMEL-17141: camel-health - Include error details for HTTP status code.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7bd9eb0159509df11ac07297f70a785ea5a8fcd8
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Nov 29 11:33:41 2021 +0100
CAMEL-17141: camel-health - Include error details for HTTP status code.
---
.../component/telegram/TelegramException.java | 58 ++++++++++++++++++++++
.../service/TelegramServiceRestBotAPIAdapter.java | 7 ++-
.../TelegramConsumerHealthCheckErrorTest.java | 23 +++++----
.../java/org/apache/camel/health/HealthCheck.java | 1 +
.../org/apache/camel/spi/HttpResponseAware.java | 43 ++++++++++++++++
.../camel/impl/health/ConsumerHealthCheck.java | 11 +++-
.../camel/support/ScheduledPollConsumer.java | 58 ++++++++++++++++++++++
.../support/ScheduledPollConsumerHealthCheck.java | 11 ++--
8 files changed, 198 insertions(+), 14 deletions(-)
diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramException.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramException.java
new file mode 100644
index 0000000..593b6a7
--- /dev/null
+++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramException.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.telegram;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.HttpResponseAware;
+
+public class TelegramException extends RuntimeCamelException implements HttpResponseAware {
+
+ private int httpResponseCode;
+ private String httpResponseStatus;
+
+ public TelegramException(String message) {
+ super(message);
+ }
+
+ public TelegramException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public TelegramException(Throwable cause) {
+ super(cause);
+ }
+
+ @Override
+ public int getHttpResponseCode() {
+ return httpResponseCode;
+ }
+
+ @Override
+ public void setHttpResponseCode(int httpResponseCode) {
+ this.httpResponseCode = httpResponseCode;
+ }
+
+ @Override
+ public String getHttpResponseStatus() {
+ return httpResponseStatus;
+ }
+
+ @Override
+ public void setHttpResponseStatus(String httpResponseStatus) {
+ this.httpResponseStatus = httpResponseStatus;
+ }
+}
diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java
index 76f01c0..6b89527 100644
--- a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java
+++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java
@@ -34,6 +34,7 @@ import io.netty.handler.codec.http.HttpHeaders;
import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.telegram.TelegramException;
import org.apache.camel.component.telegram.TelegramService;
import org.apache.camel.component.telegram.model.EditMessageCaptionMessage;
import org.apache.camel.component.telegram.model.EditMessageDelete;
@@ -154,6 +155,7 @@ public class TelegramServiceRestBotAPIAdapter implements TelegramService {
try {
final Response response = asyncHttpClient.executeRequest(request).get();
int code = response.getStatusCode();
+ String status = response.getStatusText();
if (code >= 200 && code < 300) {
try {
final String responseBody = response.getResponseBody();
@@ -166,9 +168,12 @@ public class TelegramServiceRestBotAPIAdapter implements TelegramService {
"Could not parse the response from " + request.getMethod() + " " + request.getUrl(), e);
}
} else {
- throw new RuntimeCamelException(
+ TelegramException cause = new TelegramException(
"Could not " + request.getMethod() + " " + request.getUrl() + ": " + response.getStatusCode() + " "
+ response.getStatusText());
+ cause.setHttpResponseCode(code);
+ cause.setHttpResponseStatus(status);
+ throw cause;
}
} catch (ExecutionException e) {
throw new RuntimeCamelException("Could not request " + request.getMethod() + " " + request.getUrl(), e);
diff --git a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramConsumerHealthCheckErrorTest.java b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramConsumerHealthCheckErrorTest.java
index b7a4892..c0447b6 100644
--- a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramConsumerHealthCheckErrorTest.java
+++ b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramConsumerHealthCheckErrorTest.java
@@ -69,24 +69,29 @@ public class TelegramConsumerHealthCheckErrorTest extends TelegramTestSupport {
// if we grab the health check by id, we can also check it afterwards
HealthCheck hc = hcr.getCheck("consumer:telegram").get();
+
+ // wait until we have the error
+ Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(
+ () -> {
+ HealthCheck.Result rc = hc.call();
+ Long count = (Long) rc.getDetails().get(HealthCheck.FAILURE_ERROR_COUNT);
+ return count != null && count > 0;
+ });
+
HealthCheck.Result rc = hc.call();
// and get the detailed error message (and exception)
Assertions.assertEquals(HealthCheck.State.DOWN, rc.getState());
String msg = rc.getMessage().get();
long count = (long) rc.getDetails().get(HealthCheck.FAILURE_ERROR_COUNT);
- if (count == 0) {
- Assertions.assertEquals("Consumer has not yet polled route: telegram (telegram://bots)", msg);
- } else {
- Assertions.assertEquals("Consumer failed polling " + count + " times route: telegram (telegram://bots)", msg);
- }
+ Assertions.assertEquals("Consumer failed polling " + count + " times route: telegram (telegram://bots)", msg);
Assertions.assertEquals("telegram://bots?authorizationToken=mock-token",
rc.getDetails().get(HealthCheck.FAILURE_ENDPOINT_URI));
- if (rc.getError().isPresent()) {
- Throwable e = rc.getError().get();
- Assertions.assertTrue(e.getMessage().contains("401 Unauthorized"));
- }
+ Throwable e = rc.getError().get();
+ Assertions.assertTrue(e.getMessage().contains("401 Unauthorized"));
+ // TODO: add http status code
+ // Assertions.assertEquals(401, rc.getDetails().get(HealthCheck.HTTP_RESPONSE_CODE));
}
@Override
diff --git a/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java b/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java
index bb9fd2a..1258c73 100644
--- a/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java
+++ b/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java
@@ -39,6 +39,7 @@ public interface HealthCheck extends HasGroup, HasId, Ordered {
String FAILURE_ENDPOINT_URI = "failure.endpoint.uri";
String FAILURE_ERROR_COUNT = "failure.error.count";
String SUCCESS_COUNT = "success.count";
+ String HTTP_RESPONSE_CODE = "http.response.code";
enum State {
UP,
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/HttpResponseAware.java b/core/camel-api/src/main/java/org/apache/camel/spi/HttpResponseAware.java
new file mode 100644
index 0000000..a9bf0eb
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/HttpResponseAware.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spi;
+
+/**
+ * Represents an object which is aware of HTTP responses.
+ */
+public interface HttpResponseAware {
+
+ /**
+ * The HTTP status code
+ */
+ int getHttpResponseCode();
+
+ /**
+ * Sets the HTTP status code
+ */
+ void setHttpResponseCode(int code);
+
+ /**
+ * The HTTP status line
+ */
+ String getHttpResponseStatus();
+
+ /**
+ * Sets the HTTP status line
+ */
+ void setHttpResponseStatus(String status);
+}
diff --git a/core/camel-health/src/main/java/org/apache/camel/impl/health/ConsumerHealthCheck.java b/core/camel-health/src/main/java/org/apache/camel/impl/health/ConsumerHealthCheck.java
index cd6ccf0..99ddb25 100644
--- a/core/camel-health/src/main/java/org/apache/camel/impl/health/ConsumerHealthCheck.java
+++ b/core/camel-health/src/main/java/org/apache/camel/impl/health/ConsumerHealthCheck.java
@@ -23,6 +23,7 @@ import org.apache.camel.Route;
import org.apache.camel.health.HealthCheck;
import org.apache.camel.health.HealthCheckAware;
import org.apache.camel.health.HealthCheckResultBuilder;
+import org.apache.camel.spi.HttpResponseAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,7 +62,15 @@ public class ConsumerHealthCheck extends RouteHealthCheck {
builder.message(result.getMessage().get());
}
if (result.getError().isPresent()) {
- builder.error(result.getError().get());
+ Throwable cause = result.getError().get();
+ builder.error(cause);
+ // if the caused exception is HTTP response aware then include the response status code
+ if (cause instanceof HttpResponseAware) {
+ int code = ((HttpResponseAware) cause).getHttpResponseCode();
+ if (code > 0) {
+ builder.detail(HealthCheck.HTTP_RESPONSE_CODE, code);
+ }
+ }
}
builder.details(result.getDetails());
}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java
index 3131442..d0be2cc 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java
@@ -16,10 +16,12 @@
*/
package org.apache.camel.support;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.camel.Endpoint;
@@ -29,7 +31,9 @@ import org.apache.camel.LoggingLevel;
import org.apache.camel.PollingConsumerPollingStrategy;
import org.apache.camel.Processor;
import org.apache.camel.Suspendable;
+import org.apache.camel.health.HealthCheck;
import org.apache.camel.health.HealthCheckAware;
+import org.apache.camel.spi.HttpResponseAware;
import org.apache.camel.spi.PollingConsumerPollStrategy;
import org.apache.camel.spi.ScheduledPollConsumerScheduler;
import org.apache.camel.support.service.ServiceHelper;
@@ -73,7 +77,9 @@ public abstract class ScheduledPollConsumer extends DefaultConsumer
private volatile long errorCounter;
private volatile long successCounter;
private volatile Throwable lastError;
+ private volatile Map<String, Object> lastErrorDetails;
private final AtomicLong counter = new AtomicLong();
+ private final AtomicBoolean firstPoolDone = new AtomicBoolean();
public ScheduledPollConsumer(Endpoint endpoint, Processor processor) {
super(endpoint, processor);
@@ -255,12 +261,24 @@ public abstract class ScheduledPollConsumer extends DefaultConsumer
successCounter = 0;
errorCounter++;
lastError = cause;
+ // enrich last error with http response code if possible
+ if (cause instanceof HttpResponseAware) {
+ int code = ((HttpResponseAware) cause).getHttpResponseCode();
+ if (code > 0) {
+ addLastErrorDetail(HealthCheck.HTTP_RESPONSE_CODE, code);
+ }
+ }
} else {
idleCounter = polledMessages == 0 ? ++idleCounter : 0;
successCounter++;
errorCounter = 0;
lastError = null;
+ lastErrorDetails = null;
}
+
+ // first pool is done after starting
+ firstPoolDone.set(true);
+
LOG.trace("doRun() done with idleCounter={}, successCounter={}, errorCounter={}", idleCounter, successCounter,
errorCounter);
@@ -459,6 +477,13 @@ public abstract class ScheduledPollConsumer extends DefaultConsumer
}
/**
+ * Whether a first pool attempt has been done (also if the consumer has been restarted)
+ */
+ protected boolean isFirstPoolDone() {
+ return firstPoolDone.get();
+ }
+
+ /**
* Gets the last caused error (exception) for the last poll that failed. When the consumer is successfully again,
* then the error resets to null.
*/
@@ -467,6 +492,38 @@ public abstract class ScheduledPollConsumer extends DefaultConsumer
}
/**
+ * Gets the last caused error (exception) details for the last poll that failed. When the consumer is successfully
+ * again, then the error resets to null.
+ *
+ * Some consumers can provide additional error details here, besides the caused exception. For example if the
+ * consumer uses HTTP then the {@link org.apache.camel.health.HealthCheck#HTTP_RESPONSE_CODE} can be included.
+ *
+ * @return error details, or null if no details exists.
+ */
+ protected Map<String, Object> getLastErrorDetails() {
+ return lastErrorDetails;
+ }
+
+ /**
+ * Adds a detail to the last caused error (exception) for the last poll that failed. When the consumer is
+ * successfully again, then the error resets to null.
+ *
+ * Some consumers can provide additional error details here, besides the caused exception. For example if the
+ * consumer uses HTTP then the {@link org.apache.camel.health.HealthCheck#HTTP_RESPONSE_CODE} can be included.
+ *
+ * @param key the key (see {@link org.apache.camel.health.HealthCheck})
+ * @param value the value
+ */
+ protected void addLastErrorDetail(String key, Object value) {
+ if (lastErrorDetails == null) {
+ lastErrorDetails = new HashMap<>();
+ }
+ if (lastErrorDetails != null) {
+ lastErrorDetails.put(key, value);
+ }
+ }
+
+ /**
* The polling method which is invoked periodically to poll this consumer
*
* @return number of messages polled, will be <tt>0</tt> if no message was polled at all.
@@ -570,6 +627,7 @@ public abstract class ScheduledPollConsumer extends DefaultConsumer
errorCounter = 0;
successCounter = 0;
counter.set(0);
+ firstPoolDone.set(false);
super.doStop();
}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumerHealthCheck.java b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumerHealthCheck.java
index 3839ca0..ec9fa57 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumerHealthCheck.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumerHealthCheck.java
@@ -49,11 +49,11 @@ public class ScheduledPollConsumerHealthCheck implements HealthCheck {
builder.detail(FAILURE_ENDPOINT_URI, consumer.getEndpoint().getEndpointUri());
long ec = consumer.getErrorCounter();
- long cnt = consumer.getCounter();
+ boolean first = consumer.isFirstPoolDone();
Throwable cause = consumer.getLastError();
- // can only be healthy if we have at least one poll and there are no errors
- boolean healthy = cnt > 0 && ec == 0;
+ // can only be healthy if we have at least one poll done and there are no errors
+ boolean healthy = first && ec == 0;
if (healthy) {
builder.up();
} else {
@@ -68,6 +68,11 @@ public class ScheduledPollConsumerHealthCheck implements HealthCheck {
builder.message(String.format(msg, rid, sanitizedUri));
}
builder.error(cause);
+
+ // include any additional details
+ if (consumer.getLastErrorDetails() != null) {
+ builder.details(consumer.getLastErrorDetails());
+ }
}
return builder.build();