Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f93cd84
remove unused management.health.solr config
dennisvang Jan 27, 2026
55d1925
enable actuator metrics endpoint
dennisvang Jan 27, 2026
e8223d7
disable all actuator metrics and enable only the http.* metrics
dennisvang Jan 27, 2026
36aadf4
todo actuator metrics filter
dennisvang Jan 27, 2026
13f840d
add rudimentary RequestMetricsFilter
dennisvang Jan 27, 2026
9f3a61c
only enable the custom metric
dennisvang Jan 27, 2026
377a291
add javadoc strings for RequestMetricsFilter
dennisvang Jan 28, 2026
be3ab4a
use uri instead of servletpath
dennisvang Jan 28, 2026
3dab7ce
remove redundant variables from RequestMetricsFilter
dennisvang Jan 28, 2026
b5a0045
rename custom metric to custom.http.server.requests
dennisvang Jan 28, 2026
ad0f041
add license to RequestMetricsFilter
dennisvang Jan 28, 2026
cd25737
replace OncePerRequestFilter subclass by a custom micrometer Observat…
dennisvang Jan 28, 2026
f71d893
replace custom by http.server in application.yml
dennisvang Jan 28, 2026
494adc0
replace RequestObservationFilter by ActuatorConfig
dennisvang Feb 2, 2026
cd2e98c
add license to ActuatorConfig.java
dennisvang Feb 2, 2026
f950008
bump version to 1.20.0
dennisvang May 1, 2026
3388ac8
fix license
dennisvang May 1, 2026
be24c85
Merge branch 'support/1.19.x' into feature/661-metrics
dennisvang May 20, 2026
4ef9bf4
Merge branch 'support/1.19.x' into feature/661-metrics
dennisvang May 20, 2026
ac9bee1
Merge branch 'support/1.19.x' into feature/661-metrics
dennisvang May 20, 2026
bb1488c
remove todo w.r.t. metrics filter
dennisvang May 21, 2026
4e35ae3
add TestMetrics class with metrics filter check
dennisvang May 21, 2026
13989ee
disable the http.server.requests.active metric
dennisvang May 21, 2026
4ff791f
adapt TestMetrics to expect only http.server.requests
dennisvang May 21, 2026
4232d3e
test values of http.url tag
dennisvang May 21, 2026
29f7cb3
test that endpoint visits are counted
dennisvang May 21, 2026
6bf7fff
require authorization for actuator metrics endpoints
dennisvang May 21, 2026
ce3df0c
test unauthenticated access to actuator metrics
dennisvang May 21, 2026
1f0f0f8
accept header not necessary for metrics auth test
dennisvang May 21, 2026
de02f0f
activate testing profile for TestMetrics
dennisvang May 22, 2026
086af96
sort annotations
dennisvang May 22, 2026
1ad99b9
test auth for metric uri instead of metric list uri
dennisvang May 22, 2026
582300f
specify mock user for MetricsTest authentication
dennisvang May 22, 2026
68b9da0
clear all actuator meters before each test in TestMetrics
dennisvang May 22, 2026
59353fc
rename TestMetrics to MetricsTest
dennisvang May 22, 2026
f338698
use constructor autowiring for MetricsTest
dennisvang May 22, 2026
a792d6b
Merge branch 'master' into feature/661-metrics
dennisvang Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ For older versions, we highly recommend upgrading to the latest version.

| Version | Supported |
|---------| ------------------ |
| 1.19.x | :white_check_mark: |
| < 1.19 | :x: |
| 1.20.x | :white_check_mark: |
| < 1.20 | :x: |

## Current Recommendations

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>org.fairdatateam</groupId>
<artifactId>fairdatapoint</artifactId>
<version>1.19.1</version>
<version>1.20.0</version>
<packaging>jar</packaging>

<name>FairDataPoint</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* The MIT License
* Copyright © 2017 FAIR Data Team
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.fairdatateam.fairdatapoint.config;

import io.micrometer.common.KeyValue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;

@Configuration
public class ActuatorConfig {

/**
* Replaces the default `uri` path-pattern values with full paths in actuator metrics http.server.requests
*/
@Bean
DefaultServerRequestObservationConvention customServerRequestObservationConvention() {
return new DefaultServerRequestObservationConvention() {
/**
* Replace the default URI path pattern (e.g. `/catalog/{id}`) with the full URI path (e.g. `/catalog/123`).
* This is high-cardinality data, but we add it as low-cardinality to make sure it shows up in the metrics.
* Beware, this could lead to memory issues in case of excessive number of URIs.
* See micrometer docs for more info.
* @param context ServerRequestObservationContext
* @return KeyValue with full uri
*/
@Override
protected KeyValue uri(ServerRequestObservationContext context) {
// note the resulting tag name is "http.url" instead of "uri"
return super.httpUrl(context);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
package org.fairdatateam.fairdatapoint.config;

import org.fairdatateam.fairdatapoint.api.filter.FilterConfigurer;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand Down Expand Up @@ -62,6 +63,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, FilterConfigurer filte
.requestMatchers("/index/admin**").authenticated()
.requestMatchers("/index**").permitAll()
.requestMatchers(HttpMethod.PUT).authenticated()
.requestMatchers(EndpointRequest.to("metrics")).authenticated()
.anyRequest().permitAll();
}
)
Expand Down
12 changes: 7 additions & 5 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ spring:
thread-name-prefix: fdp-task-

management:
health:
solr:
enabled: false
info:
defaults:
enabled: false
endpoints:
web:
exposure:
include: health, info
include: health, info, metrics
metrics:
enable:
all: false
http.server.requests: true
http.server.requests.active: false

security:
jwt:
Expand Down Expand Up @@ -67,7 +69,7 @@ metadataProperties:

openapi:
title: FAIR Data Point API
version: 1.19.1
version: 1.20.0
description: "The reference implementation of the metadata registration service: A service implementing the API specification. It contains an authentication system to allow maintainers to define and update metadata. Read-only access to the data is public."
contact:
name: Luiz Bonino
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* The MIT License
* Copyright © 2017 FAIR Data Team
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.fairdatateam.fairdatapoint.acceptance.actuator;

import io.micrometer.core.instrument.MeterRegistry;
import org.fairdatateam.fairdatapoint.Profiles;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithAnonymousUser;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.assertj.MvcTestResult;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

import static org.assertj.core.api.Assertions.assertThat;


@ActiveProfiles(Profiles.TESTING)
@AutoConfigureMockMvc
@SpringBootTest
@WithMockUser
class MetricsTest {

final String metricName = "http.server.requests";
final UriComponentsBuilder metricsUriBuilder = UriComponentsBuilder.fromPath("/actuator/metrics");
final String tagName = "http.url";
final String uriVisited = "/meta";

private final MeterRegistry meterRegistry;

// https://docs.spring.io/spring-framework/reference/testing/mockmvc/assertj.html
private final MockMvcTester mockMvc;

/**
* Constructor
*/
@Autowired
MetricsTest(MeterRegistry meterRegistry, MockMvcTester mockMvc) {
this.meterRegistry = meterRegistry;
this.mockMvc = mockMvc;
}

@BeforeEach
void clearMeterRegistry() {
// clear all metrics
meterRegistry.clear();
}

@Test
@WithAnonymousUser
void onlyAuthenticatedUsersCanAccessMetrics() {
MvcTestResult testResult = mockMvc.get()
.uri(metricsUriBuilder.pathSegment(metricName).build().toUri())
.exchange();
// todo: should actually be 401 UNAUTHORIZED (see #704)
assertThat(testResult).hasStatus(HttpStatus.FORBIDDEN);
}

@Test
void onlyExposesHttpServerRequestsMetric() {
// visit some endpoint to initialize the "http.server.requests" metric
mockMvc.get().uri(uriVisited).exchange();
// get metrics list
MvcTestResult testResult = mockMvc.get()
.uri(metricsUriBuilder.build().toUri())
.accept(MediaType.APPLICATION_JSON)
.exchange();
// results should only include the "http.server.requests" (this only appears if endpoints have been visited)
assertThat(testResult)
.hasStatusOk()
.hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
.bodyJson()
.extractingPath("$.names")
.asArray()
.containsExactly(metricName);
}

@Test
void listsVisitedEndpointUris() {
// visit some endpoint to initialize the "http.server.requests" metric
mockMvc.get().uri(uriVisited).exchange();
// get the available metrics for "http.server.requests"
MvcTestResult testResult = mockMvc.get()
.uri(metricsUriBuilder.pathSegment(metricName).build().toUri())
.accept(MediaType.APPLICATION_JSON)
.exchange();
// the visited endpoint should be included in the available tag values for "http.url"
assertThat(testResult).hasStatusOk().hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON);
assertThat(testResult).bodyJson().extractingPath("$.name").isEqualTo(metricName);
assertThat(testResult).bodyJson().extractingPath("$.availableTags").asArray()
.filteredOn("tag", tagName)
.flatExtracting("values")
.contains(uriVisited);
}

@Test
void endpointVisitsAreCounted() {
// visit some endpoint a number of times
final int visitCount = 3;
for (int i = 0; i < visitCount; i++) {
mockMvc.get().uri(uriVisited).exchange();
}
// get the available metrics for this endpoint
URI endpointMetricsUri = metricsUriBuilder
.pathSegment(metricName)
.queryParam("tag", "%s:%s".formatted(tagName, uriVisited))
.build().toUri();
MvcTestResult testResult = mockMvc.get()
.uri(endpointMetricsUri)
.accept(MediaType.APPLICATION_JSON)
.exchange();
// the COUNT statistic value for this endpoint should match the actual number of visits
assertThat(testResult).hasStatusOk().hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON);
assertThat(testResult).bodyJson().extractingPath("$.name").isEqualTo(metricName);
assertThat(testResult).bodyJson().extractingPath("$.measurements").asArray()
.filteredOn("statistic", "COUNT")
.extracting("value")
.contains((double) visitCount);
}
}
Loading