This is an automated email from the ASF dual-hosted git repository. ilgrosso pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push: new 259c3c83d1 [SYNCOPE-1105] Generating random UUID as operation key, storing into MDC and displaying via log4j's PatternLayout 259c3c83d1 is described below commit 259c3c83d1ecc804b7fd2bc4a4587eb68288841f Author: Francesco Chicchiriccò <ilgro...@apache.org> AuthorDate: Wed Jan 4 16:43:55 2023 +0100 [SYNCOPE-1105] Generating random UUID as operation key, storing into MDC and displaying via log4j's PatternLayout --- .../core/rest/cxf/IdRepoRESTCXFContext.java | 33 ++++++------ .../syncope/core/rest/cxf/MDCInInterceptor.java | 58 ++++++++++++++++++++++ .../core/rest/cxf/RestServiceExceptionMapper.java | 6 ++- ....java => ThreadLocalCleanupOutInterceptor.java} | 20 +++++--- .../core/provisioning/api/job/JobDelegate.java | 2 + .../java/job/AbstractInterruptableJob.java | 1 - .../java/job/AbstractSchedTaskJobDelegate.java | 13 +++++ .../java/job/report/DefaultReportJobDelegate.java | 13 +++++ docker/core/src/main/resources/log4j2.xml | 2 +- fit/core-reference/src/main/resources/log4j2.xml | 2 +- 10 files changed, 119 insertions(+), 31 deletions(-) diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java index 51755161c8..dd0d1de78e 100644 --- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java +++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadPoolExecutor; -import javax.servlet.ServletRequestListener; +import javax.validation.Validator; import org.apache.cxf.Bus; import org.apache.cxf.endpoint.Server; import org.apache.cxf.jaxrs.ext.ContextProvider; @@ -138,7 +138,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -193,15 +192,15 @@ public class IdRepoRESTCXFContext { @ConditionalOnMissingBean @Bean - public BeanValidationProvider validationProvider() { - return new BeanValidationProvider(); + public MDCInInterceptor mdcInInterceptor() { + return new MDCInInterceptor(); } @ConditionalOnMissingBean @Bean - public JAXRSBeanValidationInInterceptor validationInInterceptor(final BeanValidationProvider validationProvider) { + public JAXRSBeanValidationInInterceptor validationInInterceptor(final Validator validator) { JAXRSBeanValidationInInterceptor validationInInterceptor = new JAXRSBeanValidationInInterceptor(); - validationInInterceptor.setProvider(validationProvider); + validationInInterceptor.setProvider(new BeanValidationProvider(validator)); return validationInInterceptor; } @@ -220,6 +219,12 @@ public class IdRepoRESTCXFContext { return gzipOutInterceptor; } + @ConditionalOnMissingBean + @Bean + public ThreadLocalCleanupOutInterceptor threadLocalCleanupOutInterceptor() { + return new ThreadLocalCleanupOutInterceptor(); + } + @ConditionalOnMissingBean @Bean public RestServiceExceptionMapper restServiceExceptionMapper(final Environment env) { @@ -317,9 +322,11 @@ public class IdRepoRESTCXFContext { final JacksonXMLProvider xmlProvider, final JacksonJsonProvider jsonProvider, final DateParamConverterProvider dateParamConverterProvider, + final MDCInInterceptor mdcInInterceptor, final JAXRSBeanValidationInInterceptor validationInInterceptor, final GZIPInInterceptor gzipInInterceptor, final GZIPOutInterceptor gzipOutInterceptor, + final ThreadLocalCleanupOutInterceptor threadLocalCleanupOutInterceptor, final OpenApiFeature openapiFeature, final Bus bus, final ApplicationContext ctx, @@ -351,11 +358,9 @@ public class IdRepoRESTCXFContext { addDomainFilter, addETagFilter)); - restContainer.setInInterceptors(List.of( - gzipInInterceptor, - validationInInterceptor)); + restContainer.setInInterceptors(List.of(mdcInInterceptor, validationInInterceptor, gzipInInterceptor)); - restContainer.setOutInterceptors(List.of(gzipOutInterceptor)); + restContainer.setOutInterceptors(List.of(gzipOutInterceptor, threadLocalCleanupOutInterceptor)); restContainer.setFeatures(List.of(openapiFeature)); @@ -363,14 +368,6 @@ public class IdRepoRESTCXFContext { return restContainer.create(); } - @ConditionalOnMissingBean - @Bean - public ServletListenerRegistrationBean<ServletRequestListener> listenerRegistrationBean() { - ServletListenerRegistrationBean<ServletRequestListener> bean = new ServletListenerRegistrationBean<>(); - bean.setListener(new ThreadLocalCleanupListener()); - return bean; - } - @ConditionalOnMissingBean @Bean public AccessTokenService accessTokenService(final AccessTokenLogic accessTokenLogic) { diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/MDCInInterceptor.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/MDCInInterceptor.java new file mode 100644 index 0000000000..5117610665 --- /dev/null +++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/MDCInInterceptor.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.syncope.core.rest.cxf; + +import org.apache.cxf.interceptor.Fault; +import org.apache.cxf.message.Exchange; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.AbstractPhaseInterceptor; +import org.apache.cxf.phase.Phase; +import org.apache.syncope.core.provisioning.api.job.JobDelegate; +import org.apache.syncope.core.spring.security.SecureRandomUtils; +import org.slf4j.MDC; + +/** + * Populate MDC with sensible information, for the current thread. + * + * MDC is then cleared up by {@link org.apache.syncope.core.rest.cxf.ThreadLocalCleanupOutInterceptor} + */ +public class MDCInInterceptor extends AbstractPhaseInterceptor<Message> { + + // just same value as org.apache.cxf.ext.logging.event.LogEvent.KEY_EXCHANGE_ID + protected static final String KEY_EXCHANGE_ID = "exchangeId"; + + public MDCInInterceptor() { + super(Phase.PRE_INVOKE); + } + + @Override + public void handleMessage(final Message message) throws Fault { + Exchange exchange = message.getExchange(); + + // this ensures we are not duplicating nor conflicting with + // org.apache.cxf.ext.logging.AbstractLoggingInterceptor.createExchangeId(Message) + String exchangeId = (String) exchange.get(KEY_EXCHANGE_ID); + if (exchangeId == null) { + exchangeId = SecureRandomUtils.generateRandomUUID().toString(); + exchange.put(KEY_EXCHANGE_ID, exchangeId); + } + + MDC.put(JobDelegate.OPERATION_ID, exchangeId); + } +} diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java index d2690d0c34..8f2d253cf8 100644 --- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java +++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java @@ -320,7 +320,9 @@ public class RestServiceExceptionMapper implements ExceptionMapper<Exception> { message = env.getProperty("errMessage." + UNIQUE_MSG_KEY); } - return Optional.ofNullable(message) - .orElseGet(() -> (ex.getCause() == null) ? ex.getMessage() : ex.getCause().getMessage()); + return Optional.ofNullable(message). + orElseGet(() -> Optional.ofNullable(ex.getCause()). + map(Throwable::getMessage). + orElseGet(() -> ex.getMessage())); } } diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/ThreadLocalCleanupListener.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/ThreadLocalCleanupOutInterceptor.java similarity index 70% rename from core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/ThreadLocalCleanupListener.java rename to core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/ThreadLocalCleanupOutInterceptor.java index e6c5c39983..39b893a690 100644 --- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/ThreadLocalCleanupListener.java +++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/ThreadLocalCleanupOutInterceptor.java @@ -18,27 +18,31 @@ */ package org.apache.syncope.core.rest.cxf; -import javax.servlet.ServletRequestEvent; -import javax.servlet.ServletRequestListener; +import org.apache.cxf.interceptor.Fault; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.AbstractPhaseInterceptor; +import org.apache.cxf.phase.Phase; import org.apache.syncope.core.provisioning.api.utils.FormatUtils; import org.identityconnectors.common.l10n.CurrentLocale; import org.identityconnectors.framework.impl.api.local.ThreadClassLoaderManager; +import org.slf4j.MDC; /** - * Remove any known thread-local variable when the servlet request is destroyed. + * Removes any known thread-local variable. */ -public class ThreadLocalCleanupListener implements ServletRequestListener { +public class ThreadLocalCleanupOutInterceptor extends AbstractPhaseInterceptor<Message> { - @Override - public void requestInitialized(final ServletRequestEvent sre) { - // nothing to do while setting up this request (and thread) + public ThreadLocalCleanupOutInterceptor() { + super(Phase.POST_INVOKE); } @Override - public void requestDestroyed(final ServletRequestEvent sre) { + public void handleMessage(final Message message) throws Fault { FormatUtils.clear(); ThreadClassLoaderManager.clearInstance(); CurrentLocale.clear(); + + MDC.clear(); } } diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java index 329cad2845..9addc1b2e0 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/job/JobDelegate.java @@ -23,6 +23,8 @@ package org.apache.syncope.core.provisioning.api.job; */ public interface JobDelegate { + String OPERATION_ID = "operation.id"; + void interrupt(); boolean isInterrupted(); diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java index a858d2385b..3889ee56ae 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractInterruptableJob.java @@ -36,7 +36,6 @@ public abstract class AbstractInterruptableJob implements InterruptableJob { public boolean isInterrupted() { return false; } - }; public JobDelegate getDelegate() { diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java index cbace864bd..29d422d5cb 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java @@ -35,11 +35,13 @@ import org.apache.syncope.core.provisioning.api.job.JobManager; import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate; import org.apache.syncope.core.provisioning.api.notification.NotificationManager; import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2; +import org.apache.syncope.core.spring.security.SecureRandomUtils; import org.apache.syncope.core.spring.security.SecurityProperties; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; @@ -131,6 +133,13 @@ public abstract class AbstractSchedTaskJobDelegate<T extends SchedTask> implemen return; } + boolean manageOperationId = Optional.ofNullable(MDC.get(OPERATION_ID)). + map(operationId -> false). + orElseGet(() -> { + MDC.put(OPERATION_ID, SecureRandomUtils.generateRandomUUID().toString()); + return true; + }); + String executor = Optional.ofNullable(context.getMergedJobDataMap().getString(JobManager.EXECUTOR_KEY)). orElse(securityProperties.getAdminUser()); TaskExec<SchedTask> execution = taskUtilsFactory.getInstance(taskType).newTaskExec(); @@ -181,6 +190,10 @@ public abstract class AbstractSchedTaskJobDelegate<T extends SchedTask> implemen result, task, null); + + if (manageOperationId) { + MDC.remove(OPERATION_ID); + } } /** diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java index 1004593e9e..2ec4777791 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java @@ -46,9 +46,11 @@ import org.apache.syncope.core.provisioning.api.event.JobStatusEvent; import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate; import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2; import org.apache.syncope.core.spring.implementation.ImplementationManager; +import org.apache.syncope.core.spring.security.SecureRandomUtils; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; import org.xml.sax.helpers.AttributesImpl; @@ -135,6 +137,13 @@ public class DefaultReportJobDelegate implements ReportJobDelegate { return; } + boolean manageOperationId = Optional.ofNullable(MDC.get(OPERATION_ID)). + map(operationId -> false). + orElseGet(() -> { + MDC.put(OPERATION_ID, SecureRandomUtils.generateRandomUUID().toString()); + return true; + }); + // 1. create execution ReportExec execution = entityFactory.newEntity(ReportExec.class); execution.setStatus(ReportExecStatus.STARTED); @@ -240,6 +249,10 @@ public class DefaultReportJobDelegate implements ReportJobDelegate { execution.setMessage(reportExecutionMessage.toString()); execution.setEnd(OffsetDateTime.now()); reportExecDAO.save(execution); + + if (manageOperationId) { + MDC.remove(OPERATION_ID); + } } } } diff --git a/docker/core/src/main/resources/log4j2.xml b/docker/core/src/main/resources/log4j2.xml index 770fc91c7b..91ab9b6611 100644 --- a/docker/core/src/main/resources/log4j2.xml +++ b/docker/core/src/main/resources/log4j2.xml @@ -22,7 +22,7 @@ under the License. <appenders> <Console name="console" target="SYSTEM_OUT" follow="true"> - <PatternLayout pattern="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %highlight{${LOG_LEVEL_PATTERN:-%5p}}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=green, DEBUG=green bold, TRACE=blue} [%11.11t] %style{%-60.60c{60}}{cyan} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> + <PatternLayout pattern="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %notEmpty{[operation.id=%X{operation.id}] }%highlight{${LOG_LEVEL_PATTERN:-%5p}}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=green, DEBUG=green bold, TRACE=blue} [%11.11t] %style{%-60.60c{60}}{cyan} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> </Console> </appenders> diff --git a/fit/core-reference/src/main/resources/log4j2.xml b/fit/core-reference/src/main/resources/log4j2.xml index cdd8cc39ba..4ab455fe53 100644 --- a/fit/core-reference/src/main/resources/log4j2.xml +++ b/fit/core-reference/src/main/resources/log4j2.xml @@ -24,7 +24,7 @@ under the License. filePattern="${log.directory}/core-%d{yyyy-MM-dd}.log.gz" immediateFlush="false" append="true"> <PatternLayout> - <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern> + <pattern>%d{HH:mm:ss.SSS} %notEmpty{[operation.id=%X{operation.id}] }%-5level %logger - %msg%n</pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy/>