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

mpochatkin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 6c586fad79e IGNITE-25853 Add REST events (#7504)
6c586fad79e is described below

commit 6c586fad79e608c9517980f540f0b0a3cc4d2108
Author: Vadim Kolodin <[email protected]>
AuthorDate: Fri Feb 13 15:58:43 2026 +0400

    IGNITE-25853 Add REST events (#7504)
---
 .../internal/eventlog/api/IgniteEventType.java     |   7 +-
 .../rest/exception/handler/ErrorHandlingTest.java  |   1 +
 modules/rest/build.gradle                          |   1 +
 .../internal/rest/events/ItRestEventsTest.java     | 207 +++++++++++++++++++++
 .../ignite/internal/rest/events/RestEvents.java    | 169 +++++++++++++++++
 .../internal/rest/events/RestEventsFactory.java    |  51 +++++
 .../internal/rest/events/RestEventsFilter.java     |  73 ++++++++
 .../ignite/internal/rest/RestComponentTest.java    |   6 +-
 .../ClusterConfigurationControllerTest.java        |   2 +
 .../NodeConfigurationControllerTest.java           |   1 +
 .../rest/metrics/NodeMetricsControllerTest.java    |   1 +
 .../SystemDisasterRecoveryControllerTest.java      |   1 +
 .../org/apache/ignite/internal/app/IgniteImpl.java |   5 +-
 13 files changed, 522 insertions(+), 3 deletions(-)

diff --git 
a/modules/eventlog/src/main/java/org/apache/ignite/internal/eventlog/api/IgniteEventType.java
 
b/modules/eventlog/src/main/java/org/apache/ignite/internal/eventlog/api/IgniteEventType.java
index fdb115331ba..9e0598cdf54 100644
--- 
a/modules/eventlog/src/main/java/org/apache/ignite/internal/eventlog/api/IgniteEventType.java
+++ 
b/modules/eventlog/src/main/java/org/apache/ignite/internal/eventlog/api/IgniteEventType.java
@@ -51,7 +51,12 @@ public enum IgniteEventType {
     COMPUTE_TASK_CANCELED,
 
     QUERY_STARTED,
-    QUERY_FINISHED;
+    QUERY_FINISHED,
+
+    REST_API_REQUEST_STARTED,
+    REST_API_REQUEST_FINISHED,
+
+    ;
 
     static {
         // Without the following line, the IgniteEventType enum will not be 
registered in the EventTypeRegistry
diff --git 
a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
 
b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
index 0abaeb73610..18287ed7590 100644
--- 
a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
+++ 
b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
@@ -65,6 +65,7 @@ import org.junit.jupiter.params.provider.MethodSource;
  */
 @MicronautTest
 @Property(name = "micronaut.security.enabled", value = "false")
+@Property(name = "ignite.endpoints.rest-events", value = "false")
 @Property(name = "ignite.endpoints.filter-non-initialized", value = "false")
 public class ErrorHandlingTest {
     @Inject
diff --git a/modules/rest/build.gradle b/modules/rest/build.gradle
index 0baf552eced..365ca4e9fcd 100644
--- a/modules/rest/build.gradle
+++ b/modules/rest/build.gradle
@@ -92,6 +92,7 @@ dependencies {
     integrationTestImplementation project(':ignite-system-view-api')
     integrationTestImplementation project(':ignite-table')
     integrationTestImplementation project(':ignite-transactions')
+    integrationTestImplementation project(':ignite-eventlog')
     integrationTestImplementation testFixtures(project(':ignite-core'))
     integrationTestImplementation testFixtures(project(':ignite-runner'))
     integrationTestImplementation 
testFixtures(project(':ignite-cluster-management'))
diff --git 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/events/ItRestEventsTest.java
 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/events/ItRestEventsTest.java
new file mode 100644
index 00000000000..12042056888
--- /dev/null
+++ 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/events/ItRestEventsTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.ignite.internal.rest.events;
+
+import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInRelativeOrder;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import io.micronaut.context.annotation.Property;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.MutableHttpRequest;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.apache.ignite.InitParametersBuilder;
+import org.apache.ignite.internal.ClusterConfiguration;
+import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
+import org.apache.ignite.internal.eventlog.api.IgniteEventType;
+import org.apache.ignite.internal.eventlog.event.EventUser;
+import org.apache.ignite.internal.rest.events.RestEvents.FieldNames;
+import org.apache.ignite.internal.testframework.log4j2.EventLogInspector;
+import org.hamcrest.Matcher;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Integration test for REST events.
+ */
+@MicronautTest(rebuildContext = true)
+@Property(name = "ignite.endpoints.rest-events", value = "true")
+class ItRestEventsTest extends ClusterPerTestIntegrationTest {
+    private static final String NODE_URL = "http://localhost:"; + 
ClusterConfiguration.DEFAULT_BASE_HTTP_PORT;
+    private static final @Nullable String username = "admin";
+    private static final @Nullable String password = "password";
+
+    private EventLogInspector logInspector = new EventLogInspector();
+    private boolean securityEnabled = true;
+
+    @Inject
+    @Client(NODE_URL)
+    HttpClient client;
+
+    @BeforeEach
+    @Override
+    public void startCluster(TestInfo testInfo) {
+    }
+
+    @BeforeEach
+    void setUp() {
+        logInspector.start();
+    }
+
+    @AfterEach
+    void tearDown() {
+        logInspector.stop();
+    }
+
+    @Override
+    protected void customizeInitParameters(InitParametersBuilder builder) {
+        String allEvents = Arrays.stream(IgniteEventType.values())
+                .map(IgniteEventType::name)
+                .filter(name -> name.startsWith("REST"))
+                .collect(Collectors.joining(", ", "[", "]"));
+
+        String confEvents = "ignite.eventlog {"
+                + " sinks.logSink.channel: testChannel,"
+                + " channels.testChannel.events: " + allEvents
+                + "}";
+
+        String confSecurity = "ignite.security.enabled=" + securityEnabled + 
",\n"
+                + " ignite.security.authentication.providers.default={"
+                + " type=basic,"
+                + " users=[{username=" + username + ",password=" + password + 
"}]"
+                + "}";
+
+        builder.clusterConfiguration(confEvents + "," + confSecurity);
+    }
+
+    @ParameterizedTest(name = "securityEnabled={0}")
+    @ValueSource(booleans = {false, true})
+    void eventsTest(boolean securityEnabled, TestInfo testInfo) throws 
Exception {
+        this.securityEnabled = securityEnabled;
+
+        EventUser user = securityEnabled
+                ? EventUser.of(username, "basic")
+                : EventUser.of("anonymous", "anonymous");
+
+        super.startCluster(testInfo);
+
+        // any couple endpoints are enough for this test
+        for (String uri : List.of(
+                "/health",
+                "/management/v1/node/state"
+        )) {
+            MutableHttpRequest<Object> request = HttpRequest.GET(uri);
+            if (securityEnabled) {
+                request.header("Authorization", 
basicAuthenticationHeader(username, password));
+            }
+            assertDoesNotThrow(() -> client.toBlocking().retrieve(request));
+        }
+
+        await().until(logInspector::events, hasSize(greaterThanOrEqualTo(4)));
+
+        assertThat(logInspector.events(), containsInRelativeOrder(
+                eventEqualTo(IgniteEventType.REST_API_REQUEST_STARTED, user, 
Map.of(
+                        FieldNames.REQUEST_ID, notNullValue(),
+                        FieldNames.TIMESTAMP, notNullValue(),
+                        FieldNames.METHOD, "GET",
+                        FieldNames.ENDPOINT, "/health",
+                        FieldNames.NODE_NAME, notNullValue()
+                )),
+                eventEqualTo(IgniteEventType.REST_API_REQUEST_FINISHED, user, 
Map.of(
+                        FieldNames.REQUEST_ID, notNullValue(),
+                        FieldNames.TIMESTAMP, notNullValue(),
+                        FieldNames.METHOD, "GET",
+                        FieldNames.ENDPOINT, "/health",
+                        FieldNames.NODE_NAME, notNullValue(),
+                        FieldNames.STATUS, equalTo(200),
+                        FieldNames.DURATION_MS, notNullValue()
+                )),
+                eventEqualTo(IgniteEventType.REST_API_REQUEST_STARTED, user, 
Map.of(
+                        FieldNames.REQUEST_ID, notNullValue(),
+                        FieldNames.TIMESTAMP, notNullValue(),
+                        FieldNames.METHOD, "GET",
+                        FieldNames.ENDPOINT, "/management/v1/node/state",
+                        FieldNames.NODE_NAME, notNullValue()
+                )),
+                eventEqualTo(IgniteEventType.REST_API_REQUEST_FINISHED, user, 
Map.of(
+                        FieldNames.REQUEST_ID, notNullValue(),
+                        FieldNames.TIMESTAMP, notNullValue(),
+                        FieldNames.METHOD, "GET",
+                        FieldNames.ENDPOINT, "/management/v1/node/state",
+                        FieldNames.NODE_NAME, notNullValue(),
+                        FieldNames.STATUS, equalTo(200),
+                        FieldNames.DURATION_MS, notNullValue()
+                ))
+        ));
+    }
+
+    private static String basicAuthenticationHeader(String username, String 
password) {
+        String valueToEncode = username + ":" + password;
+        return "Basic " + 
Base64.getEncoder().encodeToString(valueToEncode.getBytes());
+    }
+
+    private static Matcher<? super String> eventEqualTo(
+            IgniteEventType type,
+            @Nullable EventUser user,
+            @Nullable Map<String, Object> fields
+    ) {
+        List<Matcher<? super String>> matchers = new ArrayList<>();
+
+        matchers.add(hasJsonPath("$.type", equalToIgnoringCase(type.name())));
+
+        if (user != null) {
+            matchers.add(hasJsonPath("$.user.username", 
equalToIgnoringCase(user.username())));
+            matchers.add(hasJsonPath("$.user.authenticationProvider", 
equalToIgnoringCase(user.authenticationProvider())));
+        }
+
+        if (fields != null) {
+            for (Entry<String, Object> entry : fields.entrySet()) {
+                if (entry.getValue() instanceof String) {
+                    matchers.add(hasJsonPath("$.fields." + entry.getKey(), 
equalToIgnoringCase((String) entry.getValue())));
+                } else if (entry.getValue() instanceof Matcher) {
+                    matchers.add(hasJsonPath("$.fields." + entry.getKey(), 
(Matcher<String>) entry.getValue()));
+                }
+
+            }
+        }
+
+        return allOf(matchers);
+    }
+}
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEvents.java
 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEvents.java
new file mode 100644
index 00000000000..71047e1cdf4
--- /dev/null
+++ 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEvents.java
@@ -0,0 +1,169 @@
+/*
+ * 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.ignite.internal.rest.events;
+
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.MutableHttpResponse;
+import io.micronaut.security.authentication.Authentication;
+import io.micronaut.security.filters.SecurityFilter;
+import java.time.Instant;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import org.apache.ignite.internal.eventlog.api.EventLog;
+import org.apache.ignite.internal.eventlog.api.IgniteEventType;
+import org.apache.ignite.internal.eventlog.event.EventUser;
+import org.apache.ignite.internal.rest.ResourceHolder;
+import org.apache.ignite.internal.util.IgniteUtils;
+
+/**
+ * REST API event factory.
+ */
+public class RestEvents implements ResourceHolder {
+    private EventLog eventLog;
+    private String localNodeName;
+
+    RestEvents(EventLog eventLog, String localNodeName) {
+        this.eventLog = eventLog;
+        this.localNodeName = localNodeName;
+    }
+
+    @Override
+    public void cleanResources() {
+        this.eventLog = null;
+        this.localNodeName = null;
+    }
+
+    void logRequestStarted(HttpRequest<?> request) {
+        long startTime = System.currentTimeMillis();
+        request.setAttribute(Attributes.REQUEST_ID, UUID.randomUUID());
+        request.setAttribute(Attributes.START_TIME, startTime);
+
+        eventLog.log(
+                IgniteEventType.REST_API_REQUEST_STARTED.name(),
+                () -> {
+                    Map<String, Object> fields = 
IgniteUtils.newLinkedHashMap(5);
+
+                    fillCommonFields(fields, request);
+
+                    return IgniteEventType.REST_API_REQUEST_STARTED.builder()
+                            .user(extractEventUser(request))
+                            .timestamp(startTime)
+                            .fields(fields)
+                            .build();
+                }
+        );
+    }
+
+    void logRequestFinished(HttpRequest<?> request, MutableHttpResponse<?> 
response) {
+        long finishTime = System.currentTimeMillis();
+        request.setAttribute(Attributes.FINISH_TIME, finishTime);
+
+        eventLog.log(
+                IgniteEventType.REST_API_REQUEST_FINISHED.name(),
+                () -> {
+                    Map<String, Object> fields = 
IgniteUtils.newLinkedHashMap(7);
+
+                    fillCommonFields(fields, request);
+
+                    fields.put(FieldNames.STATUS, 
response.getStatus().getCode());
+
+                    return IgniteEventType.REST_API_REQUEST_FINISHED.builder()
+                            .user(extractEventUser(request))
+                            .timestamp(finishTime)
+                            .fields(fields)
+                            .build();
+                }
+        );
+    }
+
+    void logRequestError(HttpRequest<?> request, Throwable throwable) {
+        long finishTime = System.currentTimeMillis();
+        request.setAttribute(Attributes.FINISH_TIME, finishTime);
+
+        eventLog.log(
+                IgniteEventType.REST_API_REQUEST_FINISHED.name(),
+                () -> {
+                    Map<String, Object> fields = 
IgniteUtils.newLinkedHashMap(7);
+
+                    fillCommonFields(fields, request);
+
+                    // Log with 500 status for unhandled errors
+                    fields.put(FieldNames.STATUS, 500);
+                    fields.put(FieldNames.MESSAGE, throwable.getMessage());
+
+                    return IgniteEventType.REST_API_REQUEST_FINISHED.builder()
+                            .user(extractEventUser(request))
+                            .timestamp(finishTime)
+                            .fields(fields)
+                            .build();
+                }
+        );
+    }
+
+    private void fillCommonFields(Map<String, Object> fields, HttpRequest<?> 
request) {
+        fields.put(FieldNames.NODE_NAME, localNodeName);
+        fields.put(FieldNames.METHOD, request.getMethod().name());
+        fields.put(FieldNames.ENDPOINT, request.getPath());
+
+        request.getAttribute(Attributes.REQUEST_ID, UUID.class)
+                .ifPresent(id -> fields.put(FieldNames.REQUEST_ID, 
id.toString()));
+
+        request.getAttribute(Attributes.START_TIME, Long.class)
+                .ifPresent(startMillis -> {
+                    fields.put(FieldNames.TIMESTAMP, 
Instant.ofEpochMilli(startMillis).toString());
+
+                    request.getAttribute(Attributes.FINISH_TIME, Long.class)
+                            .ifPresent(finishMillis -> 
fields.put(FieldNames.DURATION_MS, finishMillis - startMillis));
+                });
+
+    }
+
+    private static EventUser extractEventUser(HttpRequest<?> request) {
+        Optional<Authentication> authentication = request.getAttribute(
+                SecurityFilter.AUTHENTICATION, Authentication.class);
+
+        if (authentication.isPresent()) {
+            String username = authentication.get().getName();
+            return EventUser.of(username, "basic");
+        }
+
+        return EventUser.of("anonymous", "anonymous");
+    }
+
+    private static class Attributes {
+        static final String REQUEST_ID = "ignite.rest.request.id";
+        static final String START_TIME = "ignite.rest.request.startTime";
+        static final String FINISH_TIME = "ignite.rest.request.finishTime";
+    }
+
+    /** REST API events field names. */
+    static class FieldNames {
+        // Common fields.
+        static final String TIMESTAMP = "timestamp";
+        static final String REQUEST_ID = "requestId";
+        static final String NODE_NAME = "nodeName";
+        static final String METHOD = "method";
+        static final String ENDPOINT = "endpoint";
+
+        // Finish event fields.
+        static final String STATUS = "status";
+        static final String DURATION_MS = "durationMs";
+        static final String MESSAGE = "message";
+    }
+}
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEventsFactory.java
 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEventsFactory.java
new file mode 100644
index 00000000000..453d221b2b6
--- /dev/null
+++ 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEventsFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ignite.internal.rest.events;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.eventlog.api.EventLog;
+import org.apache.ignite.internal.rest.RestFactory;
+
+/**
+ * Factory that creates beans needed for REST API event logging.
+ */
+@Factory
+public class RestEventsFactory implements RestFactory {
+    private EventLog eventLog;
+    private String localNodeName;
+
+    /** Constructor. */
+    public RestEventsFactory(EventLog eventLog, String localNodeName) {
+        this.eventLog = eventLog;
+        this.localNodeName = localNodeName;
+    }
+
+    @Bean
+    @Singleton
+    public RestEvents restEvents() {
+        return new RestEvents(eventLog, localNodeName);
+    }
+
+    @Override
+    public void cleanResources() {
+        this.eventLog = null;
+        this.localNodeName = null;
+    }
+}
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEventsFilter.java
 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEventsFilter.java
new file mode 100644
index 00000000000..cc0abe7e1ff
--- /dev/null
+++ 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/events/RestEventsFilter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.ignite.internal.rest.events;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.core.order.Ordered;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.MutableHttpResponse;
+import io.micronaut.http.annotation.Filter;
+import io.micronaut.http.filter.HttpServerFilter;
+import io.micronaut.http.filter.ServerFilterChain;
+import io.micronaut.http.filter.ServerFilterPhase;
+import org.apache.ignite.internal.rest.ResourceHolder;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Mono;
+
+/**
+ * HTTP filter that logs REST API request start and finish events.
+ */
+@Filter(Filter.MATCH_ALL_PATTERN)
+@Requires(property = "ignite.endpoints.rest-events", value = "true", 
defaultValue = "true")
+public class RestEventsFilter implements HttpServerFilter, ResourceHolder, 
Ordered {
+    private RestEvents restEvents;
+
+    /** Constructor. */
+    public RestEventsFilter(RestEvents restEvents) {
+        this.restEvents = restEvents;
+    }
+
+    @Override
+    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, 
ServerFilterChain chain) {
+        if (restEvents != null) {
+            restEvents.logRequestStarted(request);
+        }
+
+        return Mono.from(chain.proceed(request))
+                .doOnSuccess(response -> {
+                    if (restEvents != null) {
+                        restEvents.logRequestFinished(request, response);
+                    }
+                })
+                .doOnError(throwable -> {
+                    if (restEvents != null) {
+                        restEvents.logRequestError(request, throwable);
+                    }
+                });
+    }
+
+    @Override
+    public int getOrder() {
+        return ServerFilterPhase.SECURITY.after();
+    }
+
+    @Override
+    public void cleanResources() {
+        restEvents = null;
+    }
+}
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
index 25719833021..f357a55c589 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
+++ 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
@@ -56,6 +56,7 @@ import 
org.apache.ignite.internal.rest.configuration.PresentationsFactory;
 import org.apache.ignite.internal.rest.configuration.RestConfiguration;
 import 
org.apache.ignite.internal.rest.configuration.RestExtensionConfiguration;
 import 
org.apache.ignite.internal.rest.configuration.RestExtensionConfigurationSchema;
+import org.apache.ignite.internal.rest.events.RestEventsFactory;
 import 
org.apache.ignite.internal.security.authentication.AuthenticationManager;
 import 
org.apache.ignite.internal.security.authentication.AuthenticationManagerImpl;
 import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
@@ -112,11 +113,14 @@ public class RestComponentTest extends 
BaseIgniteAbstractTest {
                 mock(ConfigurationManager.class)
         );
         Supplier<RestFactory> restManagerFactory = () -> new 
RestManagerFactory(restManager);
+        Supplier<RestFactory> restEventsFactory = () -> new 
RestEventsFactory(EventLog.NOOP, "NOOP");
 
         restComponent = new RestComponent(
                 List.of(restPresentationFactory,
                         authProviderFactory,
-                        restManagerFactory),
+                        restManagerFactory,
+                        restEventsFactory
+                ),
                 restManager,
                 restConfiguration
         );
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
index e51eb1211ea..36faa6f8881 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
+++ 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
@@ -34,6 +34,7 @@ import 
org.apache.ignite.internal.configuration.presentation.HoconPresentation;
  */
 @MicronautTest
 @Property(name = "ignite.endpoints.filter-non-initialized", value = "false")
+@Property(name = "ignite.endpoints.rest-events", value = "false")
 @Property(name = "micronaut.security.enabled", value = "false")
 class ClusterConfigurationControllerTest extends 
ConfigurationControllerBaseTest {
     @Inject
@@ -55,3 +56,4 @@ class ClusterConfigurationControllerTest extends 
ConfigurationControllerBaseTest
         return new HoconPresentation(configurationRegistry);
     }
 }
+
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
index 942c3cf83d2..e306d4fe31c 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
+++ 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
@@ -34,6 +34,7 @@ import 
org.apache.ignite.internal.configuration.presentation.HoconPresentation;
  */
 @MicronautTest
 @Property(name = "ignite.endpoints.filter-non-initialized", value = "false")
+@Property(name = "ignite.endpoints.rest-events", value = "false")
 @Property(name = "micronaut.security.enabled", value = "false")
 class NodeConfigurationControllerTest extends ConfigurationControllerBaseTest {
     @Inject
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/metrics/NodeMetricsControllerTest.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/metrics/NodeMetricsControllerTest.java
index f373dd07e58..036e3e8c901 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/metrics/NodeMetricsControllerTest.java
+++ 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/metrics/NodeMetricsControllerTest.java
@@ -39,6 +39,7 @@ import org.junit.jupiter.api.Test;
 
 @MicronautTest
 @Property(name = "ignite.endpoints.filter-non-initialized", value = "false")
+@Property(name = "ignite.endpoints.rest-events", value = "false")
 @Property(name = "micronaut.security.enabled", value = "false")
 class NodeMetricsControllerTest extends BaseIgniteAbstractTest {
     @Inject
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryControllerTest.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryControllerTest.java
index 61a7aa97693..13acf27df00 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryControllerTest.java
+++ 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryControllerTest.java
@@ -58,6 +58,7 @@ import org.mockito.Mockito;
 
 @MicronautTest
 @Property(name = "ignite.endpoints.filter-non-initialized", value = "false")
+@Property(name = "ignite.endpoints.rest-events", value = "false")
 @Property(name = "micronaut.security.enabled", value = "false")
 class SystemDisasterRecoveryControllerTest extends BaseIgniteAbstractTest {
     @Inject
diff --git 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 33a22a73a3a..24b761491b2 100644
--- 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -231,6 +231,7 @@ import 
org.apache.ignite.internal.rest.configuration.PresentationsFactory;
 import org.apache.ignite.internal.rest.configuration.RestConfiguration;
 import 
org.apache.ignite.internal.rest.configuration.RestExtensionConfiguration;
 import org.apache.ignite.internal.rest.deployment.CodeDeploymentRestFactory;
+import org.apache.ignite.internal.rest.events.RestEventsFactory;
 import org.apache.ignite.internal.rest.metrics.MetricRestFactory;
 import org.apache.ignite.internal.rest.node.NodeManagementRestFactory;
 import org.apache.ignite.internal.rest.node.NodePropertiesFactory;
@@ -1441,6 +1442,7 @@ public class IgniteImpl implements Ignite {
         Supplier<RestFactory> sqlQueryRestFactory = () -> new 
SqlQueryRestFactory(sql, killCommandHandler);
         Supplier<RestFactory> nodePropertiesRestFactory = () -> new 
NodePropertiesFactory(nodeProperties);
         Supplier<RestFactory> dataNodesRestFactory = () -> new 
DataNodesRestFactory(distributionZoneManager);
+        Supplier<RestFactory> restEventsFactory = () -> new 
RestEventsFactory(eventLog, name);
 
         RestConfiguration restConfiguration = 
nodeCfgMgr.configurationRegistry().getConfiguration(RestExtensionConfiguration.KEY).rest();
 
@@ -1457,7 +1459,8 @@ public class IgniteImpl implements Ignite {
                         systemDisasterRecoveryFactory,
                         sqlQueryRestFactory,
                         nodePropertiesRestFactory,
-                        dataNodesRestFactory
+                        dataNodesRestFactory,
+                        restEventsFactory
                 ),
                 restManager,
                 restConfiguration

Reply via email to