Skip to content

Custom actuator metric with request counter (experimental)#836

Merged
dennisvang merged 37 commits into
masterfrom
feature/661-metrics
Jun 12, 2026
Merged

Custom actuator metric with request counter (experimental)#836
dennisvang merged 37 commits into
masterfrom
feature/661-metrics

Conversation

@dennisvang

@dennisvang dennisvang commented Jan 27, 2026

Copy link
Copy Markdown
Contributor

Experimental actuator endpoint that counts requests to individual resources.
Only available for authenticated users.

Can be disabled by changing the following metrics config:

include: health, info, metrics
metrics:
enable:
all: false
http.server.requests: true
http.server.requests.active: false

The default Spring MVC actuator metric http.server.requests does not count requests for individual resources. Instead, and for a good reason, it only shows counts for the path patterns defined for the controllers, such as

path = {"", "{oUrlPrefix:[^.]+}/{oRecordId:[^.]+}"},

To count requests to individual resource URIs, we customized the DefaultServerRequestObservationConvention to replace the default uri path pattern by the full path. Also see spring observability docs.

Note

Although it is relatively quick and easy to use Spring actuator metrics for this, I'm not convinced this is the best approach. I think it would make more sense to do this at the deployment level, e.g. based on proxy logs, instead of the app level.

fixes #661

todo

usage example

To see totals and list available endpoints (they only appear in the list after they have been visited at least once):

curl http://localhost:8080/actuator/metrics/http.server.requests | jq
{
  "name": "http.server.requests",
  "baseUnit": "seconds",
  "measurements": [
    {
      "statistic": "COUNT",
      "value": 67.0
    },
    {
      "statistic": "TOTAL_TIME",
      "value": 1.3833911090000002
    },
    {
      "statistic": "MAX",
      "value": 0.089165968
    }
  ],
  "availableTags": [
    {
      "tag": "exception",
      "values": [
        "none"
      ]
    },
    {
      "tag": "method",
      "values": [
        "POST",
        "PUT",
        "GET"
      ]
    },
    {
      "tag": "http.url",
      "values": [
        "/index/admin/trigger",
        "/configs/bootstrap",
        "/index/entries",
        "/index/entries/info",
        "/index/entries/654b5878-fc8a-44db-bb47-81754617f5a5",
        "/spec",
        "/actuator/info",
        "/actuator/metrics/",
        "/actuator/metrics/http.server.requests",
        "/meta",
        "/"
      ]
    },
    {
      "tag": "error",
      "values": [
        "none"
      ]
    },
    {
      "tag": "outcome",
      "values": [
        "CLIENT_ERROR",
        "SUCCESS"
      ]
    },
    {
      "tag": "status",
      "values": [
        "404",
        "200",
        "204"
      ]
    }
  ]
}

To see details for a specific endpoint:

http://localhost:8080/actuator/metrics/http.server.requests?tag=http.url:/index/entries/654b5878-fc8a-44db-bb47-81754617f5a5 | jq
{
  "name": "http.server.requests",
  "baseUnit": "seconds",
  "measurements": [
    {
      "statistic": "COUNT",
      "value": 3.0
    },
    {
      "statistic": "TOTAL_TIME",
      "value": 0.138951876
    },
    {
      "statistic": "MAX",
      "value": 0.05697992
    }
  ],
  "availableTags": [
    {
      "tag": "exception",
      "values": [
        "none"
      ]
    },
    {
      "tag": "method",
      "values": [
        "PUT",
        "GET"
      ]
    },
    {
      "tag": "error",
      "values": [
        "none"
      ]
    },
    {
      "tag": "outcome",
      "values": [
        "SUCCESS"
      ]
    },
    {
      "tag": "status",
      "values": [
        "200"
      ]
    }
  ]
}

@dennisvang

dennisvang commented Jan 28, 2026

Copy link
Copy Markdown
Contributor Author

Initially I implemented this by extending OncePerRequestFilter and creating a custom metric.

However, a micrometer dev suggested another approach in this comment from 2018.
Do note that the classes mentioned in that comment have been deprecated in favor of the micrometer observation APIs, as mentioned in spring boot 3 release notes.

The latest and greatest approach is probably to override either DefaultServerRequestObservationConvention, or micrometer ObservationFilter, as mentioned in the observability docs.

@dennisvang dennisvang force-pushed the feature/661-metrics branch 2 times, most recently from eca3a0e to 401221b Compare January 28, 2026 19:25
@dennisvang

dennisvang commented Jan 29, 2026

Copy link
Copy Markdown
Contributor Author

ran into #839

@dennisvang

Copy link
Copy Markdown
Contributor Author

The current implementation still shows all metrics, so the filter in application.yml is ignored for some reason.

This can be checked as follows:

curl http://localhost:8080/actuator/metrics | jq

which still returns a long list, instead of just http.server.requests:

Details

{
  "names": [
    "application.ready.time",
    "application.started.time",
    "executor.active",
    "executor.completed",
    "executor.pool.core",
    "executor.pool.max",
    "executor.pool.size",
    "executor.queue.remaining",
    "executor.queued",
    "http.client.requests",
    "http.client.requests.active",
    "http.server.requests",
    "http.server.requests.active",
    "mongodb.driver.commands",
    "mongodb.driver.pool.checkedout",
    "mongodb.driver.pool.checkoutfailed",
    "mongodb.driver.pool.size",
    "mongodb.driver.pool.waitqueuesize",
    "spring.data.repository.invocations",
    "spring.security.authorizations",
    "spring.security.authorizations.active",
    "spring.security.filterchains",
    "spring.security.filterchains.CORSFilter.after",
    "spring.security.filterchains.CORSFilter.before",
    "spring.security.filterchains.JwtTokenFilter.after",
    "spring.security.filterchains.JwtTokenFilter.before",
    "spring.security.filterchains.LoggingFilter.after",
    "spring.security.filterchains.LoggingFilter.before",
    "spring.security.filterchains.access.exceptions.after",
    "spring.security.filterchains.access.exceptions.before",
    "spring.security.filterchains.active",
    "spring.security.filterchains.authentication.anonymous.after",
    "spring.security.filterchains.authentication.anonymous.before",
    "spring.security.filterchains.authorization.after",
    "spring.security.filterchains.authorization.before",
    "spring.security.filterchains.context.async.after",
    "spring.security.filterchains.context.async.before",
    "spring.security.filterchains.context.holder.after",
    "spring.security.filterchains.context.holder.before",
    "spring.security.filterchains.context.servlet.after",
    "spring.security.filterchains.context.servlet.before",
    "spring.security.filterchains.header.after",
    "spring.security.filterchains.header.before",
    "spring.security.filterchains.logout.after",
    "spring.security.filterchains.logout.before",
    "spring.security.filterchains.requestcache.after",
    "spring.security.filterchains.requestcache.before",
    "spring.security.filterchains.session.management.after",
    "spring.security.filterchains.session.management.before",
    "spring.security.filterchains.session.urlencoding.after",
    "spring.security.filterchains.session.urlencoding.before",
    "spring.security.http.secured.requests",
    "spring.security.http.secured.requests.active",
    "tomcat.sessions.active.current",
    "tomcat.sessions.active.max",
    "tomcat.sessions.alive.max",
    "tomcat.sessions.created",
    "tomcat.sessions.expired",
    "tomcat.sessions.rejected"
  ]
}

@dennisvang dennisvang force-pushed the feature/661-metrics branch from 9c7fc37 to cd2e98c Compare May 1, 2026 15:51
dennisvang added 2 commits May 1, 2026 17:54
metrics are a new feature
@dennisvang

dennisvang commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

Debugging shows that the management.metrics.enable.all: false filter has no effect here because the metrics registry is not auto-configured properly.

This is caused by conflicting annotations on the Application class and misconfiguration in the AclConfig and AclMethodSecurityConfiguration

UPDATE: Now fixed by #906

@dennisvang dennisvang marked this pull request as ready for review May 21, 2026 16:15
@dennisvang dennisvang changed the base branch from support/1.18.x to support/1.19.x May 22, 2026 12:48
@dennisvang dennisvang requested a review from luizbonino June 2, 2026 16:46
@dennisvang dennisvang added the feature Request for new functionality label Jun 9, 2026
pulling in changes w.r.t the switch from develop to master, and annotation cleanup
@dennisvang dennisvang force-pushed the feature/661-metrics branch from cf7fe1a to a792d6b Compare June 10, 2026 11:19
@dennisvang dennisvang changed the base branch from support/1.19.x to master June 10, 2026 11:32
@dennisvang dennisvang merged commit 1aa68ff into master Jun 12, 2026
12 of 13 checks passed
@dennisvang dennisvang deleted the feature/661-metrics branch June 12, 2026 12:36
@dennisvang dennisvang changed the title Custom actuator metric with request counter Custom actuator metric with request counter (experimental) Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Request for new functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Metrics gatherer

1 participant