{"id":22152130,"url":"https://github.com/findinpath/spring-retry-metrics","last_synced_at":"2025-06-23T04:36:12.516Z","repository":{"id":112791048,"uuid":"222729056","full_name":"findinpath/spring-retry-metrics","owner":"findinpath","description":"Demo on how to add metrics on the spirng-retry functionality. ","archived":false,"fork":false,"pushed_at":"2024-02-02T23:01:55.000Z","size":21,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T13:27:17.674Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.findinpath.com/spring-retry-metrics/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/findinpath.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-19T15:32:13.000Z","updated_at":"2020-06-19T23:05:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"9b4346e0-da9b-496a-85f7-d9c701788465","html_url":"https://github.com/findinpath/spring-retry-metrics","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/findinpath/spring-retry-metrics","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/findinpath%2Fspring-retry-metrics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/findinpath%2Fspring-retry-metrics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/findinpath%2Fspring-retry-metrics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/findinpath%2Fspring-retry-metrics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/findinpath","download_url":"https://codeload.github.com/findinpath/spring-retry-metrics/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/findinpath%2Fspring-retry-metrics/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261416264,"owners_count":23155036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-02T00:47:20.869Z","updated_at":"2025-06-23T04:36:07.506Z","avatar_url":"https://github.com/findinpath.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Monitoring for spring-retry\n===========================\n\nThis demo showcases how to add monitoring on the [spring-retry](https://github.com/spring-projects/spring-retry) functionality.\n\n\nThe project [spring-retry](https://github.com/spring-projects/spring-retry) offers both declarative and\nimperative retry support for [spring](https://spring.io/) applications.\n\nBelow is presented a simple code snippet to get an\nidea about how the imperative retry support can be\nconfigured for a project:\n\n```java\nRetryTemplate template = RetryTemplate.builder()\n\t\t\t\t.maxAttempts(3)\n\t\t\t\t.fixedBackoff(1000)\n\t\t\t\t.retryOn(RemoteAccessException.class)\n\t\t\t\t.build();\n\ntemplate.execute(ctx -\u003e {\n    // ... do something\n});\n```\n\nCoupling together spring AOP and spring-retry gives\nthe ability to perform retries on all the public method calls of a specific class. \nThis could come pretty handy when dealing with a class which exposes the API of an external service. \n\nDue to various reasons, every now and then it happens that an API call doesn't succeed, \nbut when trying again everything works just fine.  \n\nWhat this project showcases is the ability to monitor via [micrometer](https://micrometer.io/)\nlibrary:\n\n- how many calls to the external API were successful\n- how many calls to the external API were retried once, twice, three times\n- how many calls to the external API have failed and with which exception class \n\n\nThe test class `com.findinpath.github.api.GithubApiRetryTest` provides concrete test cases\nfor proving how the monitoring of the retry functionality works.\n\nFor the sake of a concrete example, this project makes use of a slimmed mocked version of the\nGithub API. No actual calls towards [Github API](https://api.github.com/) are made during the\ntests of this project.   \n\nBelow is presented a test scenario from the project for showing at work \nboth the spring-retry functionality (the Github repository details are\nretrieved successfully, even though the first call to the Github API fails)\nand the monitoring of the spring-retry functionality on the methods advised\nvia spring AOP for the class `com.findinpath.github.api.GithubApi` \n\n\n```java\n  @Test\n  public void firstApiOperationCallFails() throws Exception {\n    var blogRepository = new GithubRepository(BLOG_REPOSITORY_NAME,\n        new URL(\"https://github.com/findinpath/blog\"),\n        false);\n\n    when(restClient.getForEntity(\n        eq(API_URL + \"orgs/\" + ORGANISATION_NAME + \"/repos/\" + BLOG_REPOSITORY_NAME),\n        eq(GithubRepository.class))\n    )\n    .thenThrow(new IllegalStateException(\"Internal server error\"))\n    .thenReturn(blogRepository);\n\n    var repository = githubApi.getOrganisationRepository(ORGANISATION_NAME, BLOG_REPOSITORY_NAME);\n    assertThat(repository, equalTo(blogRepository));\n\n    // check that the monitoring works as expected\n    var meters = meterRegistry.getMeters();\n    var githubApiTimer = getExactlyOneMeter(meters, API_METRIC_NAME,\n        Timer.class,\n        Tag.of(\"exception\", \"none\"),\n        Tag.of(\"class\", \"GithubApi\"),\n        Tag.of(\"method\", \"getOrganisationRepository\"));\n    var githubApiExceptionTimer = getExactlyOneMeter(meters, API_METRIC_NAME,\n        Timer.class,\n        Tag.of(\"exception\", \"IllegalStateException\"),\n        Tag.of(\"class\", \"GithubApi\"),\n        Tag.of(\"method\", \"getOrganisationRepository\"));\n    var githubApiRetryTimer = getExactlyOneMeter(meters, API_RETRY_METRIC_NAME,\n        Timer.class,\n        Tag.of(\"class\", \"GithubApi\"),\n        Tag.of(\"method\", \"getOrganisationRepository\"));\n\n    assertThat(githubApiTimer.count(), equalTo(1L));\n    assertThat(githubApiExceptionTimer.count(), equalTo(1L));\n    assertThat(githubApiRetryTimer.count(), equalTo(1L));\n    assertThat(githubApiRetryTimer.max(TimeUnit.MILLISECONDS),\n        greaterThan(githubApiTimer.max(TimeUnit.MILLISECONDS)));\n    assertThat(githubApiRetryTimer.max(TimeUnit.MILLISECONDS),\n        greaterThan((double) GithubApiRetryTest.TestConfiguration.INITIAL_BACKOFF_TIME));\n\n    var githubApiRetriesCounter = getExactlyOneMeter(meters, API_METRIC_NAME + \"_retries\",\n        Counter.class,\n        Tag.of(MicrometerRetryListenerSupport.CLASS_TAG_NAME, \"GithubApi\"),\n        Tag.of(MicrometerRetryListenerSupport.METHOD_TAG_NAME, \"getOrganisationRepository\"),\n        Tag.of(MicrometerRetryListenerSupport.RETRY_TAG_NAME, \"1\"),\n        Tag.of(MicrometerRetryListenerSupport.EXCEPTION_TAG_NAME, \"IllegalStateException\"));\n    assertThat(githubApiRetriesCounter.count(), equalTo((double) 1));\n\n  }\n```\n\n\n## Spring AOP configuration \n\nThe following Spring AOP configuration serves for the following purposes:\n\n- times how long each Github API call takes to complete\n- adds spring-retry functionality on all exposed Github API calls\n- times how long each Github API call takes to complete including internal \nretries (in case that the first,second, .. call to the API doesn't succeed)   \n\n```xml\n  \u003caop:config\u003e\n    \u003caop:pointcut id=\"github-api-calls\"\n      expression=\"execution(* com.findinpath.github.api.GithubApi.*(..))  \"/\u003e\n\n    \u003c!--\n    the githubApiRetriesIncludedTimedAdvice advice wraps\n    the githubApiRetryAdvice advice and this it can provide\n    timing information for the duration of the API call\n    including retries\n     --\u003e\n    \u003caop:advisor pointcut-ref=\"github-api-calls\"\n      advice-ref=\"githubApiRetriesIncludedTimedAdvice\" order=\"1\"/\u003e\n    \u003caop:advisor pointcut-ref=\"github-api-calls\"\n      advice-ref=\"githubApiRetryAdvice\" order=\"2\"/\u003e\n    \u003c!--\n    timing advice for each (retries are not taken into account)\n    of the Github API calls.\n    --\u003e\n    \u003caop:advisor pointcut-ref=\"github-api-calls\"\n      advice-ref=\"githubApiTimedAdvice\" order=\"3\"/\u003e\n\n  \u003c/aop:config\u003e\n```\n\nNotice on the configuration above that several Spring AOP advisors are stacked on top of each\nother. The precedence of the advisor is determined by the `order` parameter of the advisor. \n\n## Spring-retry enhancements\n\nAt the time of this writing, [spring-retry](https://github.com/spring-projects/spring-retry) \nlibrary in the version `1.2.4.RELEASE` doesn't provide the ability to retrieve information\nabout the invoked method (see `org.aopalliance.intercept.MethodInvocation`) in the spring AOP context.\nSee the corresponding Github [issue](https://github.com/spring-projects/spring-retry/issues/119)\n\nThis limitation doesn't allow to access the invoked class and method name in \nthe concrete implementations of the `org.springframework.retry.listener.RetryListenerSupport`\ndefined for the spring-retry's `RetryTemplate`.\nWhat this means is that when a spring-retry failure occurs on a specific API call, we only would\nknow in the monitoring that one of the API calls failed, but not exactly which.\nFor our concrete example, if Github API has several exposed endpoints, it would be surely important\nto know via monitoring dashboards which one of these endpoints is causing failures in our application.\n\nIn order to enhance the monitoring of the methods advised with spring-retry functionality, this \nproject has performed a little enhancement on the default [spring-retry](https://github.com/spring-projects/spring-retry) functionality by adding the `MethodInvocation` of the advided method to the retry context.\nSee the code source for the class \n`org.springframework.retry.interceptor.MethodInvocationRetryOperationsInterceptor`\nfor more details.\n\n## Run the project\n\nRun the command\n\n```bash\nmvn clean install\n```\n\nfor executing the tests from this project.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffindinpath%2Fspring-retry-metrics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffindinpath%2Fspring-retry-metrics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffindinpath%2Fspring-retry-metrics/lists"}