diff --git a/core/metrics-starter/pom.xml b/core/metrics-starter/pom.xml
new file mode 100644
index 00000000000..f72bbd153b3
--- /dev/null
+++ b/core/metrics-starter/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ 4.0.0
+
+
+ org.apache.syncope
+ syncope-core
+ 4.0.2-SNAPSHOT
+
+
+ Apache Syncope Core Spring Boot Metrics
+ Apache Syncope Core Spring Boot Metrics
+ org.apache.syncope.core
+ syncope-core-metrics-starter
+ jar
+
+
+ ${basedir}/../..
+
+
+
+
+ org.apache.syncope.core
+ syncope-core-starter
+ ${project.version}
+
+
+
+ io.micrometer
+ micrometer-core
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
diff --git a/core/metrics-starter/src/main/java/org/apache/syncope/core/provisioning/java/propagation/InstrumentedPriorityPropagationTaskExecutor.java b/core/metrics-starter/src/main/java/org/apache/syncope/core/provisioning/java/propagation/InstrumentedPriorityPropagationTaskExecutor.java
new file mode 100644
index 00000000000..0ba8fba38f8
--- /dev/null
+++ b/core/metrics-starter/src/main/java/org/apache/syncope/core/provisioning/java/propagation/InstrumentedPriorityPropagationTaskExecutor.java
@@ -0,0 +1,85 @@
+/*
+ * 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.provisioning.java.propagation;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import java.util.Collection;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
+import org.apache.syncope.core.provisioning.api.AuditManager;
+import org.apache.syncope.core.provisioning.api.ConnectorManager;
+import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
+import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
+import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.core.task.AsyncTaskExecutor;
+
+public class InstrumentedPriorityPropagationTaskExecutor extends PriorityPropagationTaskExecutor {
+
+ protected final MeterRegistry meterRegistry;
+
+ public InstrumentedPriorityPropagationTaskExecutor(
+ final ConnectorManager connectorManager,
+ final ConnObjectUtils connObjectUtils,
+ final TaskDAO taskDAO,
+ final ExternalResourceDAO resourceDAO,
+ final PlainSchemaDAO plainSchemaDAO,
+ final NotificationManager notificationManager,
+ final AuditManager auditManager,
+ final TaskDataBinder taskDataBinder,
+ final AnyUtilsFactory anyUtilsFactory,
+ final TaskUtilsFactory taskUtilsFactory,
+ final OutboundMatcher outboundMatcher,
+ final PlainAttrValidationManager validator,
+ final ApplicationEventPublisher publisher,
+ final AsyncTaskExecutor taskExecutor,
+ final MeterRegistry meterRegistry) {
+
+ super(connectorManager, connObjectUtils, taskDAO, resourceDAO, plainSchemaDAO, notificationManager,
+ auditManager, taskDataBinder, anyUtilsFactory, taskUtilsFactory, outboundMatcher, validator, publisher,
+ taskExecutor);
+ this.meterRegistry = meterRegistry;
+ }
+
+ @Override
+ public PropagationReporter execute(
+ final Collection taskInfos,
+ final boolean nullPriorityAsync,
+ final String executor) {
+
+ PropagationReporter reporter = super.execute(taskInfos, nullPriorityAsync, executor);
+
+ reporter.getStatuses().forEach(status -> Counter.builder(
+ "syncope.propagation." + status.getStatus().name().toLowerCase() + ".count").
+ description("The total number of propagations with status " + status.getStatus().name()).
+ tag("resource", status.getResource()).
+ register(meterRegistry).
+ increment());
+
+ return reporter;
+ }
+}
diff --git a/core/metrics-starter/src/main/java/org/apache/syncope/core/spring/security/InstrumentedAuthDataAccessor.java b/core/metrics-starter/src/main/java/org/apache/syncope/core/spring/security/InstrumentedAuthDataAccessor.java
new file mode 100644
index 00000000000..daafe28af89
--- /dev/null
+++ b/core/metrics-starter/src/main/java/org/apache/syncope/core/spring/security/InstrumentedAuthDataAccessor.java
@@ -0,0 +1,171 @@
+/*
+ * 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.spring.security;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
+import org.apache.syncope.core.persistence.api.EncryptorManager;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.AuditManager;
+import org.apache.syncope.core.provisioning.api.ConnectorManager;
+import org.apache.syncope.core.provisioning.api.MappingManager;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.core.Authentication;
+
+public class InstrumentedAuthDataAccessor extends AuthDataAccessor {
+
+ protected static final String SUCCESS_TYPE = ".success";
+
+ protected static final String FAILURE_TYPE = ".failure";
+
+ protected static final String MUST_CHANGE_PASSWORD_TYPE = ".failure";
+
+ protected static final String NOT_FOUND_TYPE = ".notfound";
+
+ protected static final String DISABLED_TYPE = ".disabled";
+
+ protected static final Function USERNAME = suffix -> "syncope.auth.username" + suffix + ".count";
+
+ protected static final Function JWT = suffix -> "syncope.auth.jwt" + suffix + ".count";
+
+ protected static final Function SUCCESS_DESC =
+ type -> "The total number of succeeded " + type + " logins";
+
+ protected static final Function FAILURE_DESC =
+ type -> "The total number of failed " + type + " logins";
+
+ protected static final Function MUST_CHANGE_PASSWORD_DESC =
+ type -> "The total number of mustChangePassword users attempting to perform " + type + " login";
+
+ protected static final Function NOT_FOUND_DESC =
+ type -> "The total number of not found users attempting to perform " + type + " login";
+
+ protected static final Function DISABLED_DESC =
+ type -> "The total number of disabled users attempting to perform " + type + " login";
+
+ protected final MeterRegistry meterRegistry;
+
+ public InstrumentedAuthDataAccessor(
+ final SecurityProperties securityProperties,
+ final EncryptorManager encryptorManager,
+ final RealmSearchDAO realmSearchDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final AnySearchDAO anySearchDAO,
+ final AccessTokenDAO accessTokenDAO,
+ final ConfParamOps confParamOps,
+ final RoleDAO roleDAO,
+ final DelegationDAO delegationDAO,
+ final ExternalResourceDAO resourceDAO,
+ final ConnectorManager connectorManager,
+ final AuditManager auditManager,
+ final MappingManager mappingManager,
+ final List jwtSSOProviders,
+ final MeterRegistry meterRegistry) {
+
+ super(securityProperties, encryptorManager, realmSearchDAO, userDAO, groupDAO, anySearchDAO, accessTokenDAO,
+ confParamOps, roleDAO, delegationDAO, resourceDAO, connectorManager, auditManager, mappingManager,
+ jwtSSOProviders);
+ this.meterRegistry = meterRegistry;
+ }
+
+ @Override
+ public Triple authenticate(final String domain, final Authentication authentication) {
+ try {
+ Triple auth = super.authenticate(domain, authentication);
+
+ Optional.ofNullable(auth.getLeft()).ifPresentOrElse(
+ user -> {
+ Counter.builder(auth.getMiddle() ? USERNAME.apply(SUCCESS_TYPE) : USERNAME.apply(FAILURE_TYPE)).
+ description(auth.getMiddle()
+ ? SUCCESS_DESC.apply("username") : FAILURE_DESC.apply("username")).
+ tag("realm", user.getRealm().getFullPath()).
+ register(meterRegistry).
+ increment();
+ if (user.isMustChangePassword()) {
+ Counter.builder(USERNAME.apply(MUST_CHANGE_PASSWORD_TYPE)).
+ description(MUST_CHANGE_PASSWORD_DESC.apply("username")).
+ register(meterRegistry).
+ increment();
+ }
+ },
+ () -> Counter.builder(USERNAME.apply(NOT_FOUND_TYPE)).
+ description(NOT_FOUND_DESC.apply("username")).
+ register(meterRegistry).
+ increment());
+
+ return auth;
+ } catch (DisabledException e) {
+ Counter.builder(USERNAME.apply(DISABLED_TYPE)).
+ description(DISABLED_DESC.apply("JWT")).
+ register(meterRegistry).
+ increment();
+ throw e;
+ }
+ }
+
+ @Override
+ public Pair> authenticate(final JWTAuthentication authentication) {
+ try {
+ Pair> auth = super.authenticate(authentication);
+
+ if (MUST_CHANGE_PASSWORD_AUTHORITIES.equals(auth.getRight())) {
+ Counter.builder(JWT.apply(MUST_CHANGE_PASSWORD_TYPE)).
+ description(MUST_CHANGE_PASSWORD_DESC.apply("JWT")).
+ register(meterRegistry).
+ increment();
+ } else {
+ Counter.builder(JWT.apply(SUCCESS_TYPE)).
+ description(SUCCESS_DESC.apply("JWT")).
+ register(meterRegistry).
+ increment();
+ }
+
+ return auth;
+ } catch (AuthenticationCredentialsNotFoundException e) {
+ Counter.builder(JWT.apply(NOT_FOUND_TYPE)).
+ description(NOT_FOUND_DESC.apply("JWT")).
+ register(meterRegistry).
+ increment();
+ throw e;
+ } catch (DisabledException e) {
+ Counter.builder(JWT.apply(DISABLED_TYPE)).
+ description(DISABLED_DESC.apply("JWT")).
+ register(meterRegistry).
+ increment();
+ throw e;
+ }
+ }
+}
diff --git a/core/metrics-starter/src/main/java/org/apache/syncope/core/starter/JPAMetricsContext.java b/core/metrics-starter/src/main/java/org/apache/syncope/core/starter/JPAMetricsContext.java
new file mode 100644
index 00000000000..4e455b7e2f5
--- /dev/null
+++ b/core/metrics-starter/src/main/java/org/apache/syncope/core/starter/JPAMetricsContext.java
@@ -0,0 +1,83 @@
+/*
+ * 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.starter;
+
+import com.zaxxer.hikari.HikariDataSource;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.binder.MeterBinder;
+import java.util.List;
+import javax.sql.DataSource;
+import org.apache.syncope.core.persistence.api.DomainHolder;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.actuate.metrics.data.MetricsRepositoryMethodInvocationListener;
+import org.springframework.boot.actuate.metrics.jdbc.DataSourcePoolMetrics;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration;
+import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.function.SingletonSupplier;
+
+@ConditionalOnClass(HikariDataSource.class)
+@Import(DataSourcePoolMetadataProvidersConfiguration.class)
+@Configuration(proxyBeanMethods = false)
+public class JPAMetricsContext {
+
+ @Bean
+ public MeterBinder dataSourcePoolMetadataMeterBinder(
+ final ObjectProvider metadataProviders,
+ final DomainHolder domainHolder) {
+
+ return new MeterBinder() {
+
+ @Override
+ public void bindTo(final MeterRegistry registry) {
+ List metadataProvidersList = metadataProviders.stream().toList();
+ domainHolder.getDomains().forEach((name, dataSource) -> new DataSourcePoolMetrics(
+ dataSource, metadataProvidersList, name, List.of()).bindTo(registry));
+ }
+ };
+ }
+
+ @Bean
+ public static BeanPostProcessor jpaRepositoryFactoryBeanPostProcessor(
+ final ObjectProvider metricsRepositoryMethodInvocationListener) {
+
+ return new BeanPostProcessor() {
+
+ @Override
+ public Object postProcessBeforeInitialization(final Object bean, final String beanName)
+ throws BeansException {
+
+ if (bean instanceof JpaRepositoryFactory jpaRepositoryFactory) {
+ MetricsRepositoryMethodInvocationListener listener =
+ SingletonSupplier.of(metricsRepositoryMethodInvocationListener::getObject).get();
+ Assert.state(listener != null, "'listener' must not be null");
+ jpaRepositoryFactory.addInvocationListener(listener);
+ }
+ return bean;
+ }
+ };
+ }
+}
diff --git a/core/metrics-starter/src/main/java/org/apache/syncope/core/starter/MetricsContext.java b/core/metrics-starter/src/main/java/org/apache/syncope/core/starter/MetricsContext.java
new file mode 100644
index 00000000000..f7720ada027
--- /dev/null
+++ b/core/metrics-starter/src/main/java/org/apache/syncope/core/starter/MetricsContext.java
@@ -0,0 +1,137 @@
+/*
+ * 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.starter;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import java.util.List;
+import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
+import org.apache.syncope.core.persistence.api.EncryptorManager;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
+import org.apache.syncope.core.provisioning.api.AuditManager;
+import org.apache.syncope.core.provisioning.api.ConnectorManager;
+import org.apache.syncope.core.provisioning.api.MappingManager;
+import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
+import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.core.provisioning.java.propagation.InstrumentedPriorityPropagationTaskExecutor;
+import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
+import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
+import org.apache.syncope.core.spring.security.AuthDataAccessor;
+import org.apache.syncope.core.spring.security.InstrumentedAuthDataAccessor;
+import org.apache.syncope.core.spring.security.JWTSSOProvider;
+import org.apache.syncope.core.spring.security.SecurityProperties;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.AsyncTaskExecutor;
+
+@Configuration(proxyBeanMethods = false)
+public class MetricsContext {
+
+ @ConditionalOnMissingBean(name = "instrumentedAuthDataAccessor")
+ @Bean(name = { "authDataAccessor", "instrumentedAuthDataAccessor" })
+ public AuthDataAccessor instrumentedAuthDataAccessor(
+ final SecurityProperties securityProperties,
+ final EncryptorManager encryptorManager,
+ final RealmSearchDAO realmSearchDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final AnySearchDAO anySearchDAO,
+ final AccessTokenDAO accessTokenDAO,
+ final ConfParamOps confParamOps,
+ final RoleDAO roleDAO,
+ final DelegationDAO delegationDAO,
+ final ExternalResourceDAO resourceDAO,
+ final ConnectorManager connectorManager,
+ final AuditManager auditManager,
+ final MappingManager mappingManager,
+ final List jwtSSOProviders,
+ final MeterRegistry meterRegistry) {
+
+ return new InstrumentedAuthDataAccessor(
+ securityProperties,
+ encryptorManager,
+ realmSearchDAO,
+ userDAO,
+ groupDAO,
+ anySearchDAO,
+ accessTokenDAO,
+ confParamOps,
+ roleDAO,
+ delegationDAO,
+ resourceDAO,
+ connectorManager,
+ auditManager,
+ mappingManager,
+ jwtSSOProviders,
+ meterRegistry);
+ }
+
+ @ConditionalOnMissingBean(name = "instrumentedPropagationTaskExecutor")
+ @Bean(name = { "propagationTaskExecutor", "instrumentedPropagationTaskExecutor" })
+ public PropagationTaskExecutor propagationTaskExecutor(
+ @Qualifier("propagationTaskExecutorAsyncExecutor")
+ final AsyncTaskExecutor propagationTaskExecutorAsyncExecutor,
+ final TaskUtilsFactory taskUtilsFactory,
+ final AnyUtilsFactory anyUtilsFactory,
+ final ConnectorManager connectorManager,
+ final ConnObjectUtils connObjectUtils,
+ final TaskDAO taskDAO,
+ final ExternalResourceDAO resourceDAO,
+ final PlainSchemaDAO plainSchemaDAO,
+ final NotificationManager notificationManager,
+ final AuditManager auditManager,
+ final TaskDataBinder taskDataBinder,
+ final OutboundMatcher outboundMatcher,
+ final PlainAttrValidationManager validator,
+ final ApplicationEventPublisher publisher,
+ final MeterRegistry meterRegistry) {
+
+ return new InstrumentedPriorityPropagationTaskExecutor(
+ connectorManager,
+ connObjectUtils,
+ taskDAO,
+ resourceDAO,
+ plainSchemaDAO,
+ notificationManager,
+ auditManager,
+ taskDataBinder,
+ anyUtilsFactory,
+ taskUtilsFactory,
+ outboundMatcher,
+ validator,
+ publisher,
+ propagationTaskExecutorAsyncExecutor,
+ meterRegistry);
+ }
+}
diff --git a/core/metrics-starter/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsContext.java b/core/metrics-starter/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsContext.java
new file mode 100644
index 00000000000..da04a795a89
--- /dev/null
+++ b/core/metrics-starter/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsContext.java
@@ -0,0 +1,27 @@
+/*
+ * 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.springframework.boot.actuate.autoconfigure.metrics.cache;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+@Import({ CacheMeterBinderProvidersConfiguration.class, CacheMetricsRegistrarConfiguration.class })
+@Configuration(proxyBeanMethods = false)
+public class CacheMetricsContext {
+}
diff --git a/core/metrics-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/core/metrics-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000000..4ceb3ba1afd
--- /dev/null
+++ b/core/metrics-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,19 @@
+# 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.
+org.apache.syncope.core.starter.MetricsContext
+org.apache.syncope.core.starter.JPAMetricsContext
+org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsContext
diff --git a/core/pom.xml b/core/pom.xml
index 6f7912ca735..571c25a44ab 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -91,6 +91,7 @@ under the License.
workflow-java
starter
self-keymaster-starter
+ metrics-starter
persistence-jpa-upgrader
diff --git a/core/self-keymaster-starter/pom.xml b/core/self-keymaster-starter/pom.xml
index 0d0b615fccc..ea55b47b6e5 100644
--- a/core/self-keymaster-starter/pom.xml
+++ b/core/self-keymaster-starter/pom.xml
@@ -102,6 +102,23 @@ under the License.
+
+ org.apache.syncope.core
+ syncope-core-metrics-starter
+ ${project.version}
+
+
+
+ org.apache.syncope.core.idm
+ syncope-core-idm-rest-cxf
+ ${project.version}
+
+
+ org.apache.syncope.core.am
+ syncope-core-am-rest-cxf
+ ${project.version}
+
+
org.postgresql
postgresql
diff --git a/core/self-keymaster-starter/src/test/resources/core-debug.properties b/core/self-keymaster-starter/src/test/resources/core-debug.properties
index 6328313cfd3..05e963d8628 100644
--- a/core/self-keymaster-starter/src/test/resources/core-debug.properties
+++ b/core/self-keymaster-starter/src/test/resources/core-debug.properties
@@ -20,7 +20,7 @@ service.discovery.address=http://localhost:9080/syncope/rest/
logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
-management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache
+management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,metrics
keymaster.address=http://localhost:9080/syncope/rest/keymaster
keymaster.username=${anonymousUser}
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index 3f03c8a3e08..f251b9b9461 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -251,7 +251,7 @@ public Triple authenticate(final String domain, final Aut
}
if (userModified) {
- userDAO.save(user);
+ user = userDAO.save(user);
}
}
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 50d4770937d..8502930dc95 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -69,6 +69,11 @@ under the License.
syncope-core-self-keymaster-starter
${project.version}
+
+ org.apache.syncope.core
+ syncope-core-metrics-starter
+ ${project.version}
+
org.apache.syncope.common.keymaster
diff --git a/fit/core-reference/src/main/resources/core-embedded.properties b/fit/core-reference/src/main/resources/core-embedded.properties
index ae526112808..d44c2cc4846 100644
--- a/fit/core-reference/src/main/resources/core-embedded.properties
+++ b/fit/core-reference/src/main/resources/core-embedded.properties
@@ -16,7 +16,7 @@
# under the License.
embedded.databases=syncope,syncopetwo,syncopetest
-management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache
+management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,metrics
keymaster.address=http://localhost:9080/syncope/rest/keymaster
keymaster.username=${anonymousUser}
diff --git a/src/main/asciidoc/reference-guide/usage/loggers.adoc b/src/main/asciidoc/reference-guide/usage/loggers.adoc
new file mode 100644
index 00000000000..fd96f23c90a
--- /dev/null
+++ b/src/main/asciidoc/reference-guide/usage/loggers.adoc
@@ -0,0 +1,26 @@
+//
+// 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.
+//
+=== Loggers
+
+Spring Boot actuator includes the ability to
+https://docs.spring.io/spring-boot/3.4/reference/actuator/loggers.html[view and configure the log levels^] of all
+Syncope modules at runtime.
+
+In addition, Console provides a UI to view the list of logger’s configuration and set their level, for all
+Syncope modules.
diff --git a/src/main/asciidoc/reference-guide/usage/metrics.adoc b/src/main/asciidoc/reference-guide/usage/metrics.adoc
new file mode 100644
index 00000000000..98a697acbe2
--- /dev/null
+++ b/src/main/asciidoc/reference-guide/usage/metrics.adoc
@@ -0,0 +1,55 @@
+//
+// 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.
+//
+=== Metrics
+
+Monitoring performance to ensure reliability and efficiency can be achieved by leveraging
+https://docs.spring.io/spring-boot/3.4/reference/actuator/metrics.html[Spring Boot's metrics^].
+
+[[metrics-core]]
+==== Core
+
+This can be enabled by adding the following dependency to `core/pom.xml`:
+
+[source,xml,subs="verbatim,attributes"]
+----
+
+ org.apache.syncope.core
+ syncope-core-metrics-starter
+ ${syncope.version}
+
+----
+
+Additional dependencies might be required, depending on the
+https://docs.spring.io/spring-boot/3.4/reference/actuator/metrics.html#actuator.metrics.export[actual monitoring system in use^].
+
+[[metrics-wa]]
+==== WA
+
+This can be enabled by adding the following dependency to `wa/pom.xml`:
+
+[source,xml,subs="verbatim,attributes"]
+----
+
+ org.apereo.cas
+ cas-server-support-metrics
+ ${cas.version}
+
+----
+
+For further options and configuration, refer to https://apereo.github.io/cas/7.2.x/monitoring/Configuring-Metrics.html[CAS documentation^].
diff --git a/src/main/asciidoc/reference-guide/usage/usage.adoc b/src/main/asciidoc/reference-guide/usage/usage.adoc
index cc5e240be58..778903020b8 100644
--- a/src/main/asciidoc/reference-guide/usage/usage.adoc
+++ b/src/main/asciidoc/reference-guide/usage/usage.adoc
@@ -39,3 +39,7 @@ include::clientlibrary.adoc[]
include::customization.adoc[]
include::actuator.adoc[]
+
+include::loggers.adoc[]
+
+include::metrics.adoc[]