This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch hlog
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 2d5de4eb9e1a75a379eaf5b9d4a9f37ca22c4b39
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Tue Dec 10 13:10:14 2024 +0100

    CAMEL-21468: camel-http - Allow to log request/repsonse HTTP raw data to 
see what is sent over the wire.
---
 .../catalog/beans/LoggingHttpActivityListener.json |  2 +-
 .../LoggingHttpActivityListenerConfigurer.java     |  6 ++
 .../camel/bean/LoggingHttpActivityListener.json    |  2 +-
 .../apache/camel/component/http/HttpComponent.java |  1 +
 .../apache/camel/component/http/HttpEndpoint.java  |  2 +
 .../apache/camel/component/http/HttpProducer.java  | 22 ++++-
 .../http/LoggingHttpActivityListener.java          | 94 +++++++++++++++-------
 7 files changed, 97 insertions(+), 32 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/LoggingHttpActivityListener.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/LoggingHttpActivityListener.json
index 19ca316a584..9e6db510a8a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/LoggingHttpActivityListener.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/LoggingHttpActivityListener.json
@@ -10,7 +10,7 @@
     "groupId": "org.apache.camel",
     "artifactId": "camel-http",
     "version": "4.10.0-SNAPSHOT",
-    "properties": { "showRouteId": { "index": 0, "kind": "property", 
"displayName": "Show Route Id", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": "true", "description": "Show route ID." }, 
"showRouteGroup": { "index": 1, "kind": "property", "displayName": "Show Route 
Group", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaul [...]
+    "properties": { "showRouteId": { "index": 0, "kind": "property", 
"displayName": "Show Route Id", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": "true", "description": "Show route ID." }, 
"showRouteGroup": { "index": 1, "kind": "property", "displayName": "Show Route 
Group", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaul [...]
   }
 }
 
diff --git 
a/components/camel-http/src/generated/java/org/apache/camel/component/http/LoggingHttpActivityListenerConfigurer.java
 
b/components/camel-http/src/generated/java/org/apache/camel/component/http/LoggingHttpActivityListenerConfigurer.java
index 073035688d4..bdd0202c8d9 100644
--- 
a/components/camel-http/src/generated/java/org/apache/camel/component/http/LoggingHttpActivityListenerConfigurer.java
+++ 
b/components/camel-http/src/generated/java/org/apache/camel/component/http/LoggingHttpActivityListenerConfigurer.java
@@ -36,6 +36,8 @@ public class LoggingHttpActivityListenerConfigurer extends 
org.apache.camel.supp
         case "showRouteGroup": target.setShowRouteGroup(property(camelContext, 
boolean.class, value)); return true;
         case "showrouteid":
         case "showRouteId": target.setShowRouteId(property(camelContext, 
boolean.class, value)); return true;
+        case "showstreams":
+        case "showStreams": target.setShowStreams(property(camelContext, 
boolean.class, value)); return true;
         case "sourcelocationloggername":
         case "sourceLocationLoggerName": 
target.setSourceLocationLoggerName(property(camelContext, boolean.class, 
value)); return true;
         default: return false;
@@ -58,6 +60,8 @@ public class LoggingHttpActivityListenerConfigurer extends 
org.apache.camel.supp
         case "showRouteGroup": return boolean.class;
         case "showrouteid":
         case "showRouteId": return boolean.class;
+        case "showstreams":
+        case "showStreams": return boolean.class;
         case "sourcelocationloggername":
         case "sourceLocationLoggerName": return boolean.class;
         default: return null;
@@ -81,6 +85,8 @@ public class LoggingHttpActivityListenerConfigurer extends 
org.apache.camel.supp
         case "showRouteGroup": return target.isShowRouteGroup();
         case "showrouteid":
         case "showRouteId": return target.isShowRouteId();
+        case "showstreams":
+        case "showStreams": return target.isShowStreams();
         case "sourcelocationloggername":
         case "sourceLocationLoggerName": return 
target.isSourceLocationLoggerName();
         default: return null;
diff --git 
a/components/camel-http/src/generated/resources/META-INF/services/org/apache/camel/bean/LoggingHttpActivityListener.json
 
b/components/camel-http/src/generated/resources/META-INF/services/org/apache/camel/bean/LoggingHttpActivityListener.json
index 19ca316a584..9e6db510a8a 100644
--- 
a/components/camel-http/src/generated/resources/META-INF/services/org/apache/camel/bean/LoggingHttpActivityListener.json
+++ 
b/components/camel-http/src/generated/resources/META-INF/services/org/apache/camel/bean/LoggingHttpActivityListener.json
@@ -10,7 +10,7 @@
     "groupId": "org.apache.camel",
     "artifactId": "camel-http",
     "version": "4.10.0-SNAPSHOT",
-    "properties": { "showRouteId": { "index": 0, "kind": "property", 
"displayName": "Show Route Id", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": "true", "description": "Show route ID." }, 
"showRouteGroup": { "index": 1, "kind": "property", "displayName": "Show Route 
Group", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaul [...]
+    "properties": { "showRouteId": { "index": 0, "kind": "property", 
"displayName": "Show Route Id", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": "true", "description": "Show route ID." }, 
"showRouteGroup": { "index": 1, "kind": "property", "displayName": "Show Route 
Group", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaul [...]
   }
 }
 
diff --git 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
index 396cb4773da..218d316bf72 100644
--- 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
+++ 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
@@ -1068,6 +1068,7 @@ public class HttpComponent extends HttpCommonComponent 
implements RestProducerFa
             clientConnectionManager.close();
             clientConnectionManager = null;
         }
+        ServiceHelper.stopService(httpActivityListener);
 
         super.doStop();
     }
diff --git 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java
 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java
index 7df8d469e07..3ca8bf6eef8 100644
--- 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java
+++ 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java
@@ -332,6 +332,8 @@ public class HttpEndpoint extends HttpCommonEndpoint 
implements LineNumberAware
         if (httpClient instanceof Closeable closeable) {
             IOHelper.close(closeable);
         }
+        ServiceHelper.stopService(httpActivityListener);
+        super.doStop();
     }
 
     // Properties
diff --git 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
index 93c550035a0..211c61ad244 100644
--- 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
+++ 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
@@ -37,6 +37,7 @@ import java.util.Map.Entry;
 import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePropertyKey;
+import org.apache.camel.LineNumberAware;
 import org.apache.camel.Message;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.TypeConverter;
@@ -83,7 +84,7 @@ import org.apache.hc.core5.http.protocol.HttpContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class HttpProducer extends DefaultProducer {
+public class HttpProducer extends DefaultProducer implements LineNumberAware {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(HttpProducer.class);
 
@@ -813,4 +814,23 @@ public class HttpProducer extends DefaultProducer {
         this.httpClient = httpClient;
     }
 
+    @Override
+    public int getLineNumber() {
+        return getEndpoint().getLineNumber();
+    }
+
+    @Override
+    public void setLineNumber(int lineNumber) {
+        // noop
+    }
+
+    @Override
+    public String getLocation() {
+        return getEndpoint().getLocation();
+    }
+
+    @Override
+    public void setLocation(String location) {
+        // noop
+    }
 }
diff --git 
a/components/camel-http/src/main/java/org/apache/camel/component/http/LoggingHttpActivityListener.java
 
b/components/camel-http/src/main/java/org/apache/camel/component/http/LoggingHttpActivityListener.java
index cda6dc06c85..1b96727b1d2 100644
--- 
a/components/camel-http/src/main/java/org/apache/camel/component/http/LoggingHttpActivityListener.java
+++ 
b/components/camel-http/src/main/java/org/apache/camel/component/http/LoggingHttpActivityListener.java
@@ -35,8 +35,10 @@ import 
org.apache.camel.support.processor.DefaultMaskingFormatter;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
 
 import static org.apache.camel.support.LoggerHelper.getLineNumberLoggerName;
 
@@ -59,8 +61,13 @@ public class LoggingHttpActivityListener extends 
ServiceSupport implements Camel
     private boolean showExchangeId = true;
     @Metadata(defaultValue = "true", description = "Show the HTTP body.")
     private boolean showBody = true;
+    @Metadata(defaultValue = "true",
+              description = "Whether to show HTTP body that are streaming 
based. Beware that Camel will have to read the content into memory to print to 
log, and will re-create the HttpEntity stored on the request/response object. 
If you have large payloads then this can impact performance.")
+    private boolean showStreams = true;
     @Metadata(defaultValue = "true", label = "formatting", description = "Show 
the HTTP headers.")
     private boolean showHeaders = true;
+    @Metadata(defaultValue = "50000", description = "Limits the number of 
characters logged from the HTTP body.")
+    private int maxChars = 50000;
     @Metadata(description = "If true, mask sensitive information like password 
or passphrase in the log.")
     private Boolean logMask;
     @Metadata(defaultValue = "true", description = "If enabled then each 
information is outputted as separate LOG events.")
@@ -79,29 +86,6 @@ public class LoggingHttpActivityListener extends 
ServiceSupport implements Camel
         }
     }
 
-    private CamelLogger getLogger(Object source, Exchange exchange) {
-        String name = null;
-        if (sourceLocationLoggerName) {
-            name = getLineNumberLoggerName(source);
-        }
-        if (name == null) {
-            name = LoggingHttpActivityListener.class.getName();
-        }
-        LoggingLevel level = LoggingLevel.INFO;
-        if (loggingLevel != null && !loggingLevel.equals("INFO")) {
-            level = LoggingLevel.valueOf(loggingLevel);
-        }
-        return new CamelLogger(name, level);
-    }
-
-    private String getValue(boolean sensitive, Object value) {
-        String v = value != null ? value.toString() : null;
-        if (v != null && (sensitive || logMask != null && logMask)) {
-            v = maskingFormatter.format(v);
-        }
-        return v;
-    }
-
     @Override
     public void onRequestSubmitted(Object source, Exchange exchange, HttpHost 
httpHost, ClassicHttpRequest request) {
         CamelLogger logger = getLogger(source, exchange);
@@ -170,12 +154,33 @@ public class LoggingHttpActivityListener extends 
ServiceSupport implements Camel
             lines.add(""); // empty line before body
             try {
                 var e = request != null ? request.getEntity() : 
response.getEntity();
-                if (e != null && e.isRepeatable()) {
-                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
-                    e.writeTo(bos);
-                    lines.add(getValue(false, bos.toString()));
-                } else if (e != null) {
-                    lines.add("WARN: Cannot log HTTP body because the body is 
not repeatable");
+                if (e != null) {
+                    if (e.isStreaming() && !showStreams) {
+                        lines.add("WARN: Cannot log HTTP body because the body 
is streaming");
+                    } else {
+                        ByteArrayOutputStream bos = new 
ByteArrayOutputStream();
+                        e.writeTo(bos);
+                        String data = bos.toString();
+                        if (data.length() > maxChars) {
+                            data = data.substring(0, maxChars) + " ... [Body 
clipped after " + maxChars
+                                   + " chars, total length is " + 
data.length() + "]";
+                        }
+                        lines.add(getValue(false, data));
+                        if (!e.isRepeatable()) {
+                            // need to re-create body as stream is EOL
+                            byte[] arr = bos.toByteArray();
+                            ContentType ct = null;
+                            if (e.getContentType() != null) {
+                                ct = ContentType.parse(e.getContentType());
+                            }
+                            e = new ByteArrayEntity(arr, ct);
+                            if (request != null) {
+                                request.setEntity(e);
+                            } else {
+                                response.setEntity(e);
+                            }
+                        }
+                    }
                 }
             } catch (Exception e) {
                 // ignore
@@ -193,6 +198,29 @@ public class LoggingHttpActivityListener extends 
ServiceSupport implements Camel
         }
     }
 
+    private CamelLogger getLogger(Object source, Exchange exchange) {
+        String name = null;
+        if (sourceLocationLoggerName) {
+            name = getLineNumberLoggerName(source);
+        }
+        if (name == null) {
+            name = LoggingHttpActivityListener.class.getName();
+        }
+        LoggingLevel level = LoggingLevel.INFO;
+        if (loggingLevel != null && !loggingLevel.equals("INFO")) {
+            level = LoggingLevel.valueOf(loggingLevel);
+        }
+        return new CamelLogger(name, level);
+    }
+
+    private String getValue(boolean sensitive, Object value) {
+        String v = value != null ? value.toString() : null;
+        if (v != null && (sensitive || logMask != null && logMask)) {
+            v = maskingFormatter.format(v);
+        }
+        return v;
+    }
+
     @Override
     public CamelContext getCamelContext() {
         return camelContext;
@@ -243,6 +271,14 @@ public class LoggingHttpActivityListener extends 
ServiceSupport implements Camel
         this.showBody = showBody;
     }
 
+    public boolean isShowStreams() {
+        return showStreams;
+    }
+
+    public void setShowStreams(boolean showStreams) {
+        this.showStreams = showStreams;
+    }
+
     public boolean isShowHeaders() {
         return showHeaders;
     }

Reply via email to