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