diff --git a/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/main/java/io/gravitee/repository/elasticsearch/log/ElasticLogRepository.java b/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/main/java/io/gravitee/repository/elasticsearch/log/ElasticLogRepository.java index 3de293fc8e4..251095c9b7a 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/main/java/io/gravitee/repository/elasticsearch/log/ElasticLogRepository.java +++ b/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/main/java/io/gravitee/repository/elasticsearch/log/ElasticLogRepository.java @@ -71,6 +71,8 @@ public class ElasticLogRepository extends AbstractElasticsearchRepository implem @Autowired protected RepositoryConfiguration configuration; + private static final int MAX_RESULT_WINDOW = 10000; + @Override public TabularResponse query(final QueryContext queryContext, final TabularQuery query) throws AnalyticsException { final Long from = query.timeRange().range().from(); @@ -89,8 +91,8 @@ public TabularResponse query(final QueryContext queryContext, final TabularQuery } else { final TabularQuery logQuery = tabular() .timeRange(query.timeRange().range(), query.timeRange().interval()) - .page(query.page()) - .size(query.size()) + .page(1) + .size(MAX_RESULT_WINDOW) .query(logQueryString) .build(); final String sQuery = this.createElasticsearchJsonQuery(logQuery); @@ -114,7 +116,7 @@ public TabularResponse query(final QueryContext queryContext, final TabularQuery final String requestQuery = isEmpty(queryString) ? logIdsQuery : format("(%s) AND (%s)", queryString, logIdsQuery); final TabularQueryBuilder requestQueryBuilder = tabular() .timeRange(query.timeRange().range(), query.timeRange().interval()) - .page(1) + .page(query.page()) .size(query.size()) .sort(query.sort()) .query(requestQuery); @@ -129,12 +131,7 @@ public TabularResponse query(final QueryContext queryContext, final TabularQuery } SearchResponse searchResponseRequest = result.blockingGet(); - return this.toTabularResponse( - searchResponseRequest, - searchResponseRequest.getSearchHits().getTotal().getValue() == query.size() || query.page() > 1 - ? searchResponseLog.getSearchHits().getTotal().getValue() - : searchResponseRequest.getSearchHits().getTotal().getValue() - ); + return this.toTabularResponse(searchResponseRequest, searchResponseRequest.getSearchHits().getTotal().getValue()); } } catch (final Exception eex) { logger.error("Impossible to perform log request", eex); diff --git a/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/test/java/io/gravitee/repository/elasticsearch/log/ElasticsearchLogRepositoryTest.java b/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/test/java/io/gravitee/repository/elasticsearch/log/ElasticsearchLogRepositoryTest.java index 6405964d311..7726686ccec 100644 --- a/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/test/java/io/gravitee/repository/elasticsearch/log/ElasticsearchLogRepositoryTest.java +++ b/gravitee-apim-repository/gravitee-apim-repository-elasticsearch/src/test/java/io/gravitee/repository/elasticsearch/log/ElasticsearchLogRepositoryTest.java @@ -134,4 +134,221 @@ public void testTabular_withoutQuery() throws Exception { assertThat(response).isNotNull(); assertThat(response.getLogs().size()).isEqualTo(response.getSize()); } + + @Test + public void testTabular_withCombinedFilters_statusAndBody() throws Exception { + TabularResponse response = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(1) + .size(10) + .build() + ); + + assertThat(response).isNotNull(); + assertThat(response.getSize()).isLessThanOrEqualTo(6); + assertThat(response.getLogs()).hasSizeLessThanOrEqualTo((int) response.getSize()); + } + + @Test + public void testTabular_withCombinedFilters_pagination_page1() throws Exception { + TabularResponse response = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(1) + .size(3) + .build() + ); + + assertThat(response).isNotNull(); + long totalSize = response.getSize(); + assertThat(response.getLogs()).hasSizeLessThanOrEqualTo(3); + assertThat(totalSize).isGreaterThanOrEqualTo(response.getLogs().size()); + } + + @Test + public void testTabular_withCombinedFilters_pagination_page2() throws Exception { + TabularResponse responsePage1 = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(1) + .size(3) + .build() + ); + + TabularResponse responsePage2 = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(2) + .size(3) + .build() + ); + + assertThat(responsePage2).isNotNull(); + assertThat(responsePage2.getSize()).isEqualTo(responsePage1.getSize()); + int expectedPage2Size = (int) Math.max(0, responsePage1.getSize() - 3); + assertThat(responsePage2.getLogs()).hasSizeLessThanOrEqualTo(expectedPage2Size); + } + + @Test + public void testTabular_withCombinedFilters_differentPageSizes() throws Exception { + TabularResponse responseSize10 = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(1) + .size(10) + .build() + ); + + TabularResponse responseSize15 = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(1) + .size(15) + .build() + ); + + TabularResponse responseSize100 = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(1) + .size(100) + .build() + ); + assertThat(responseSize10.getSize()).isEqualTo(responseSize15.getSize()).isEqualTo(responseSize100.getSize()); + } + + @Test + public void testTabular_bodyFilterOnly_pagination_consistent() throws Exception { + String bodyQuery = "client-response.body:*not valid or is expired*"; + + TabularResponse response1 = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query(bodyQuery).page(1).size(10).build() + ); + + TabularResponse response2 = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query(bodyQuery).page(1).size(15).build() + ); + + // Total should be consistent + assertThat(response1.getSize()).isEqualTo(response2.getSize()); + assertThat(response1.getSize()).isEqualTo(6); + } + + @Test + public void testTabular_emptyResults_withCombinedFilters() throws Exception { + TabularResponse response = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:999 AND client-response.body:*nonexistent-text-12345*") + .page(1) + .size(10) + .build() + ); + + assertThat(response).isNotNull(); + assertThat(response.getSize()).isZero(); + assertThat(response.getLogs()).isEmpty(); + } + + @Test + public void testTabular_statusFilterOnly() throws Exception { + TabularResponse response = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query("status:200").page(1).size(10).build() + ); + + assertThat(response).isNotNull(); + assertThat(response.getSize()).isGreaterThan(0); + assertThat(response.getLogs()).isNotEmpty(); + } + + @Test + public void testTabular_multipleStatusFilters_withBody() throws Exception { + TabularResponse response = logRepository.query( + queryContext, + tabular() + .timeRange(lastDays(60), hours(1)) + .query("status:200 AND client-response.body:*not valid or is expired*") + .page(1) + .size(10) + .build() + ); + + assertThat(response).isNotNull(); + assertThat(response.getLogs()).hasSizeLessThanOrEqualTo((int) response.getSize()); + } + + @Test + public void testTabular_paginationBeyondResults() throws Exception { + TabularResponse responsePage1 = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query("client-response.body:*not valid or is expired*").page(1).size(10).build() + ); + + // Request page 10 when we only have 6 results + TabularResponse responsePage10 = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query("client-response.body:*not valid or is expired*").page(10).size(10).build() + ); + + assertThat(responsePage10).isNotNull(); + assertThat(responsePage10.getSize()).isEqualTo(responsePage1.getSize()); // Total should be same + assertThat(responsePage10.getLogs()).isEmpty(); // No results on page 10 + } + + @Test + public void testTabular_largePaginationSize() throws Exception { + TabularResponse response = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query("client-response.body:*not valid or is expired*").page(1).size(1000).build() + ); + + assertThat(response).isNotNull(); + assertThat(response.getSize()).isEqualTo(6); + assertThat(response.getLogs()).hasSize(6); // Should return all available results + } + + @Test + public void testTabular_caseInsensitiveBodySearch() throws Exception { + TabularResponse responseLower = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query("client-response.body:*not valid*").page(1).size(10).build() + ); + + TabularResponse responseUpper = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query("client-response.body:*NOT VALID*").page(1).size(10).build() + ); + assertThat(responseLower).isNotNull(); + assertThat(responseUpper).isNotNull(); + } + + @Test + public void testTabular_complexQuery_multipleConditions() throws Exception { + TabularResponse response = logRepository.query( + queryContext, + tabular().timeRange(lastDays(60), hours(1)).query("status:200 AND client-response.body:*valid*").page(1).size(10).build() + ); + + assertThat(response).isNotNull(); + assertThat(response.getLogs()).hasSizeLessThanOrEqualTo((int) response.getSize()); + } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/LogsServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/LogsServiceImpl.java index 06affa54431..b398093fbb9 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/LogsServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/LogsServiceImpl.java @@ -155,7 +155,7 @@ public SearchLogResponse findByApi(final ExecutionContext execut logResponse.setLogs(response.getLogs().stream().map(this::toApiRequestItem).collect(Collectors.toList())); // Add metadata (only if they are results) - if (response.getSize() > 0) { + if (response.getLogs() != null && !response.getLogs().isEmpty()) { Map> metadata = new HashMap<>(); logResponse