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

ilgrosso pushed a commit to branch 4_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/4_0_X by this push:
     new 677b99f752 [SYNCOPE-1916] Metrics (#1195)
677b99f752 is described below

commit 677b99f75205667f7650f7c9c5f766a3950dab37
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Thu Sep 25 13:55:51 2025 +0200

    [SYNCOPE-1916] Metrics (#1195)
---
 core/metrics-starter/pom.xml                       |  68 ++++++++
 ...nstrumentedPriorityPropagationTaskExecutor.java |  85 ++++++++++
 .../security/InstrumentedAuthDataAccessor.java     | 171 +++++++++++++++++++++
 .../syncope/core/starter/JPAMetricsContext.java    |  83 ++++++++++
 .../syncope/core/starter/MetricsContext.java       | 137 +++++++++++++++++
 .../metrics/cache/CacheMetricsContext.java         |  27 ++++
 ...rk.boot.autoconfigure.AutoConfiguration.imports |  19 +++
 core/pom.xml                                       |   1 +
 core/self-keymaster-starter/pom.xml                |  17 ++
 .../src/test/resources/core-debug.properties       |   2 +-
 .../core/spring/security/AuthDataAccessor.java     |   2 +-
 fit/core-reference/pom.xml                         |   5 +
 .../src/main/resources/core-embedded.properties    |   2 +-
 .../usage/{usage.adoc => loggers.adoc}             |  27 +---
 .../asciidoc/reference-guide/usage/metrics.adoc    |  55 +++++++
 src/main/asciidoc/reference-guide/usage/usage.adoc |   4 +
 16 files changed, 681 insertions(+), 24 deletions(-)

diff --git a/core/metrics-starter/pom.xml b/core/metrics-starter/pom.xml
new file mode 100644
index 0000000000..f72bbd153b
--- /dev/null
+++ b/core/metrics-starter/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope</groupId>
+    <artifactId>syncope-core</artifactId>
+    <version>4.0.2-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Core Spring Boot Metrics</name>
+  <description>Apache Syncope Core Spring Boot Metrics</description>
+  <groupId>org.apache.syncope.core</groupId>
+  <artifactId>syncope-core-metrics-starter</artifactId>
+  <packaging>jar</packaging>
+  
+  <properties>
+    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.syncope.core</groupId>
+      <artifactId>syncope-core-starter</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>io.micrometer</groupId>
+      <artifactId>micrometer-core</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+    </plugins>
+
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+  </build>
+</project>
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 0000000000..0ba8fba38f
--- /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<PropagationTaskInfo> 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 0000000000..daafe28af8
--- /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<String, String> USERNAME = suffix -> 
"syncope.auth.username" + suffix + ".count";
+
+    protected static final Function<String, String> JWT = suffix -> 
"syncope.auth.jwt" + suffix + ".count";
+
+    protected static final Function<String, String> SUCCESS_DESC =
+            type -> "The total number of succeeded " + type + " logins";
+
+    protected static final Function<String, String> FAILURE_DESC =
+            type -> "The total number of failed " + type + " logins";
+
+    protected static final Function<String, String> MUST_CHANGE_PASSWORD_DESC =
+            type -> "The total number of mustChangePassword users attempting 
to perform " + type + " login";
+
+    protected static final Function<String, String> NOT_FOUND_DESC =
+            type -> "The total number of not found users attempting to perform 
" + type + " login";
+
+    protected static final Function<String, String> 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<JWTSSOProvider> 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<User, Boolean, String> authenticate(final String domain, 
final Authentication authentication) {
+        try {
+            Triple<User, Boolean, String> 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<String, Set<SyncopeGrantedAuthority>> authenticate(final 
JWTAuthentication authentication) {
+        try {
+            Pair<String, Set<SyncopeGrantedAuthority>> 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 0000000000..4e455b7e2f
--- /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<DataSourcePoolMetadataProvider> 
metadataProviders,
+            final DomainHolder<DataSource> domainHolder) {
+
+        return new MeterBinder() {
+
+            @Override
+            public void bindTo(final MeterRegistry registry) {
+                List<DataSourcePoolMetadataProvider> 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> 
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 0000000000..f7720ada02
--- /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<JWTSSOProvider> 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 0000000000..da04a795a8
--- /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 0000000000..4ceb3ba1af
--- /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 6f7912ca73..571c25a44a 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -91,6 +91,7 @@ under the License.
     <module>workflow-java</module>
     <module>starter</module>
     <module>self-keymaster-starter</module>
+    <module>metrics-starter</module>
     <module>persistence-jpa-upgrader</module>
   </modules>
 </project>
diff --git a/core/self-keymaster-starter/pom.xml 
b/core/self-keymaster-starter/pom.xml
index 0d0b615fcc..ea55b47b6e 100644
--- a/core/self-keymaster-starter/pom.xml
+++ b/core/self-keymaster-starter/pom.xml
@@ -102,6 +102,23 @@ under the License.
       </properties>
 
       <dependencies>
+        <dependency>
+          <groupId>org.apache.syncope.core</groupId>
+          <artifactId>syncope-core-metrics-starter</artifactId>
+          <version>${project.version}</version>          
+        </dependency>
+
+        <dependency>
+          <groupId>org.apache.syncope.core.idm</groupId>
+          <artifactId>syncope-core-idm-rest-cxf</artifactId>
+          <version>${project.version}</version>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.syncope.core.am</groupId>
+          <artifactId>syncope-core-am-rest-cxf</artifactId>
+          <version>${project.version}</version>
+        </dependency>
+
         <dependency>
           <groupId>org.postgresql</groupId>
           <artifactId>postgresql</artifactId>
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 6328313cfd..05e963d862 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 3f03c8a3e0..f251b9b946 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 class AuthDataAccessor {
             }
 
             if (userModified) {
-                userDAO.save(user);
+                user = userDAO.save(user);
             }
         }
 
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 50d4770937..8502930dc9 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -69,6 +69,11 @@ under the License.
       <artifactId>syncope-core-self-keymaster-starter</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.core</groupId>
+      <artifactId>syncope-core-metrics-starter</artifactId>
+      <version>${project.version}</version>
+    </dependency>
 
     <dependency>
       <groupId>org.apache.syncope.common.keymaster</groupId>
diff --git a/fit/core-reference/src/main/resources/core-embedded.properties 
b/fit/core-reference/src/main/resources/core-embedded.properties
index ae52611280..d44c2cc484 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/usage.adoc 
b/src/main/asciidoc/reference-guide/usage/loggers.adoc
similarity index 54%
copy from src/main/asciidoc/reference-guide/usage/usage.adoc
copy to src/main/asciidoc/reference-guide/usage/loggers.adoc
index cc5e240be5..fd96f23c90 100644
--- a/src/main/asciidoc/reference-guide/usage/usage.adoc
+++ b/src/main/asciidoc/reference-guide/usage/loggers.adoc
@@ -16,26 +16,11 @@
 // specific language governing permissions and limitations
 // under the License.
 //
-== Usage
+=== Loggers
 
-Before proceeding, please ensure that you have access to a running Apache 
Syncope deployment.
-You can take a look at the
-ifeval::["{backend}" == "html5"]
-https://syncope.apache.org/docs/4.0/getting-started.html[Apache Syncope 
Getting Started Guide]
-endif::[]
-ifeval::["{backend}" == "pdf"]
-https://syncope.apache.org/docs/4.0/getting-started.pdf[Apache Syncope Getting 
Started Guide]
-endif::[]
-to check system requirements and to choose among the various options for 
obtaining Apache Syncope.
+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. 
 
-include::adminconsole.adoc[]
-
-include::enduser.adoc[]
-
-include::core.adoc[]
-
-include::clientlibrary.adoc[]
-
-include::customization.adoc[]
-
-include::actuator.adoc[]
+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 0000000000..98a697acbe
--- /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"]
+----
+<dependency>
+  <groupId>org.apache.syncope.core</groupId>
+  <artifactId>syncope-core-metrics-starter</artifactId>
+  <version>${syncope.version}</version>
+</dependency>
+----
+
+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"]
+----
+<dependency>
+  <groupId>org.apereo.cas</groupId>
+  <artifactId>cas-server-support-metrics</artifactId>
+  <version>${cas.version}</version>
+</dependency>
+----
+
+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 cc5e240be5..778903020b 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[]


Reply via email to