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}. */