From 19fc1d328362bc8821503dc8705505173701dcfc Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Tue, 2 Jun 2026 22:14:29 +0200 Subject: [PATCH] CAMEL-23662: camel-log - Route logMask setting not overriding context logMask for log component The log component endpoint (LogEndpoint) resolved the logMask decision at endpoint creation time, consulting only the endpoint URI parameter and the CamelContext, but never the route's logMask setting. This caused logMask=false on a route to be ignored when using . CamelLogProcessor now checks the route's logMask at process time via the exchange's UnitOfWork, respecting the precedence: endpoint > route > context. This also handles the inverse case where a route enables masking while the context has it disabled. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Claus Ibsen --- .../camel/component/log/LogEndpoint.java | 5 +- .../log/LogMaskRouteOverrideTest.java | 119 ++++++++++++++++++ .../support/processor/CamelLogProcessor.java | 60 ++++++++- 3 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 core/camel-core/src/test/java/org/apache/camel/component/log/LogMaskRouteOverrideTest.java diff --git a/components/camel-log/src/main/java/org/apache/camel/component/log/LogEndpoint.java b/components/camel-log/src/main/java/org/apache/camel/component/log/LogEndpoint.java index 1d45a0e1caff2..d58bdec70e84a 100644 --- a/components/camel-log/src/main/java/org/apache/camel/component/log/LogEndpoint.java +++ b/components/camel-log/src/main/java/org/apache/camel/component/log/LogEndpoint.java @@ -297,9 +297,12 @@ protected Processor createLogger() { Long groupDelay = getGroupDelay(); answer = new ThroughputLogger(camelLogger, this.getCamelContext(), getGroupInterval(), groupDelay, groupActiveOnly); } else { - answer = new CamelLogProcessor( + CamelLogProcessor clp = new CamelLogProcessor( camelLogger, localFormatter, getMaskingFormatter(), getCamelContext().getCamelContextExtension().getLogListeners()); + clp.setRouteLogMaskAware(true); + clp.setEndpointLogMask(logMask); + answer = clp; } // the logger is the processor setProcessor(answer); diff --git a/core/camel-core/src/test/java/org/apache/camel/component/log/LogMaskRouteOverrideTest.java b/core/camel-core/src/test/java/org/apache/camel/component/log/LogMaskRouteOverrideTest.java new file mode 100644 index 0000000000000..0a95d645f64bf --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/component/log/LogMaskRouteOverrideTest.java @@ -0,0 +1,119 @@ +/* + * 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.log; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.spi.MaskingFormatter; +import org.apache.camel.spi.Registry; +import org.apache.camel.support.DefaultRegistry; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LogMaskRouteOverrideTest { + + protected Registry registry; + + protected CamelContext createCamelContext() throws Exception { + registry = new DefaultRegistry(); + CamelContext context = new DefaultCamelContext(registry); + context.addRoutes(createRouteBuilder()); + return context; + } + + @Test + public void testRouteLogMaskFalseOverridesContextTrue() throws Exception { + CamelContext context = createCamelContext(); + TrackingMaskingFormatter tracker = new TrackingMaskingFormatter(); + registry.bind(MaskingFormatter.CUSTOM_LOG_MASK_REF, tracker); + context.setLogMask(true); + context.start(); + + MockEndpoint masked = context.getEndpoint("mock:masked", MockEndpoint.class); + masked.expectedMessageCount(1); + MockEndpoint noMask = context.getEndpoint("mock:noMask", MockEndpoint.class); + noMask.expectedMessageCount(1); + + // send to the route WITH masking (context default) + tracker.received = null; + context.createProducerTemplate().sendBody("direct:masked", "password=secret123"); + masked.assertIsSatisfied(); + assertNotNull(tracker.received, "Masking formatter should have been called for the masked route"); + + // send to the route with logMask=false override + tracker.received = null; + context.createProducerTemplate().sendBody("direct:noMask", "password=secret123"); + noMask.assertIsSatisfied(); + assertNull(tracker.received, "Masking formatter should NOT have been called for the noMask route"); + + context.stop(); + } + + @Test + public void testRouteLogMaskTrueOverridesContextFalse() throws Exception { + CamelContext context = createCamelContext(); + TrackingMaskingFormatter tracker = new TrackingMaskingFormatter(); + registry.bind(MaskingFormatter.CUSTOM_LOG_MASK_REF, tracker); + context.setLogMask(false); + context.start(); + + MockEndpoint forceMask = context.getEndpoint("mock:forceMask", MockEndpoint.class); + forceMask.expectedMessageCount(1); + + tracker.received = null; + context.createProducerTemplate().sendBody("direct:forceMask", "password=secret123"); + forceMask.assertIsSatisfied(); + assertNotNull(tracker.received, "Masking formatter should have been called for the forceMask route"); + assertTrue(tracker.received.contains("password=secret123")); + + context.stop(); + } + + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:masked").routeId("masked") + .to("log:masked.logger") + .to("mock:masked"); + + from("direct:noMask").routeId("noMask").logMask("false") + .to("log:noMask.logger") + .to("mock:noMask"); + + from("direct:forceMask").routeId("forceMask").logMask("true") + .to("log:forceMask.logger") + .to("mock:forceMask"); + } + }; + } + + public static class TrackingMaskingFormatter implements MaskingFormatter { + private String received; + + @Override + public String format(String source) { + received = source; + return source; + } + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/CamelLogProcessor.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/CamelLogProcessor.java index cd06ad998e90f..a435775d37071 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/processor/CamelLogProcessor.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/CamelLogProcessor.java @@ -22,6 +22,7 @@ import org.apache.camel.Exchange; import org.apache.camel.LoggingLevel; import org.apache.camel.Processor; +import org.apache.camel.Route; import org.apache.camel.spi.CamelLogger; import org.apache.camel.spi.ExchangeFormatter; import org.apache.camel.spi.IdAware; @@ -29,6 +30,7 @@ import org.apache.camel.spi.MaskingFormatter; import org.apache.camel.spi.RouteIdAware; import org.apache.camel.spi.StepIdAware; +import org.apache.camel.spi.UnitOfWork; import org.apache.camel.support.AsyncProcessorSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,6 +53,8 @@ public class CamelLogProcessor extends AsyncProcessorSupport implements IdAware, private ExchangeFormatter formatter; private MaskingFormatter maskingFormatter; private final Set listeners; + private boolean routeLogMaskAware; + private Boolean endpointLogMask; public CamelLogProcessor() { this(new CamelLogger(CamelLogProcessor.class.getName())); @@ -109,7 +113,7 @@ public void setStepId(String stepId) { public boolean process(Exchange exchange, AsyncCallback callback) { if (logger.shouldLog()) { String output = formatter.format(exchange); - if (maskingFormatter != null) { + if (shouldMask(exchange)) { output = maskingFormatter.format(output); } output = fireListeners(exchange, output); @@ -123,7 +127,7 @@ public boolean process(Exchange exchange, AsyncCallback callback) { public void process(Exchange exchange, Throwable exception) { if (logger.shouldLog()) { String output = formatter.format(exchange); - if (maskingFormatter != null) { + if (shouldMask(exchange)) { output = maskingFormatter.format(output); } output = fireListeners(exchange, output); @@ -135,7 +139,7 @@ public void process(Exchange exchange, Throwable exception) { public void process(Exchange exchange, String message) { if (logger.shouldLog()) { String output = formatter.format(exchange) + message; - if (maskingFormatter != null) { + if (shouldMask(exchange)) { output = maskingFormatter.format(output); } output = fireListeners(exchange, output); @@ -204,6 +208,56 @@ public void setMaskingFormatter(MaskingFormatter maskingFormatter) { this.maskingFormatter = maskingFormatter; } + public void setRouteLogMaskAware(boolean routeLogMaskAware) { + this.routeLogMaskAware = routeLogMaskAware; + } + + public void setEndpointLogMask(Boolean endpointLogMask) { + this.endpointLogMask = endpointLogMask; + } + + private boolean shouldMask(Exchange exchange) { + if (!routeLogMaskAware) { + return maskingFormatter != null; + } + + // endpoint-level explicit setting takes highest priority + if (endpointLogMask != null) { + if (!endpointLogMask) { + return false; + } + if (maskingFormatter == null) { + resolveMaskingFormatter(exchange); + } + return maskingFormatter != null; + } + + // check route-level logMask (falls back to context-level) + UnitOfWork uow = exchange.getUnitOfWork(); + if (uow != null) { + Route route = uow.getRoute(); + if (route != null) { + if (!route.isLogMask()) { + return false; + } + if (maskingFormatter == null) { + resolveMaskingFormatter(exchange); + } + return maskingFormatter != null; + } + } + + return maskingFormatter != null; + } + + private void resolveMaskingFormatter(Exchange exchange) { + maskingFormatter = exchange.getContext().getRegistry() + .lookupByNameAndType(MaskingFormatter.CUSTOM_LOG_MASK_REF, MaskingFormatter.class); + if (maskingFormatter == null) { + maskingFormatter = new DefaultMaskingFormatter(exchange.getContext()); + } + } + /** * {@link ExchangeFormatter} that calls toString on the {@link Exchange}. */