diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitState.java b/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitState.java index 84f956b..9e63b91 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitState.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitState.java @@ -47,27 +47,55 @@ public void recalculateStatus( this.actualRequestsPerSecond = actualRequestsPerSecond; this.idealRequestsPerSecond = idealRequestsPerSecond; - var newStatus = this.getNewAbsoluteStatus(); - LOGGER.trace( - "Current status is {} and possible new Status is {}.", - currentStatus, newStatus - ); - if (newStatus.isBetterThen(currentStatus)) { - this.lowerStatusByOne(); - } else { - this.setActualStatus(newStatus); - } + this.changeStatusAccordingToExpectedStatus(currentStatus); LOGGER.debug("New Status is {}.", this.getStatus()); }); } + private void changeStatusAccordingToExpectedStatus(ApiRateLimitStatus currentStatus) { + var idealNewStatus = this.getNewAbsoluteStatus(); + + var statusComparison = idealNewStatus.compareTo(currentStatus); + if (statusComparison > 0) { + this.lowerStatusByOne(); + } else if (statusComparison < 0) { + this.raiseStatusByOne(); + } + + logStatusChange(currentStatus, idealNewStatus); + } + + private void logStatusChange( + ApiRateLimitStatus oldStatus, + ApiRateLimitStatus idealNewStatus + ) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info( + "Status has been recalculated with actual {} / ideal {} and would ideally become {}", + String.format("%.2f", this.actualRequestsPerSecond), + String.format("%.2f", this.idealRequestsPerSecond), + idealNewStatus + ); + LOGGER.info( + "Status will instead be changed from [{}] to {}.", + oldStatus, this.getStatus() + ); + } + } + public void lowerStatusByOne() { this.status.ifPresent(currentStatus -> this.setActualStatus(currentStatus.getNextBest()) ); } + public void raiseStatusByOne() { + this.status.ifPresent(currentStatus -> + this.setActualStatus(currentStatus.getNextWorst()) + ); + } + public ApiRateLimitStatus getNewAbsoluteStatus() { ApiRateLimitStatus newStatus; if (this.idealRequestsPerSecond * this.goodLimit >= this.actualRequestsPerSecond) { diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitStatus.java b/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitStatus.java index 1fe34b7..838d0e6 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitStatus.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/ApiRateLimitStatus.java @@ -1,13 +1,11 @@ package be.xplore.githubmetrics.domain.apistate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Arrays; +import java.util.function.IntUnaryOperator; public enum ApiRateLimitStatus { CRITICAL, WARNING, CONCERNING, GOOD, OK; - private static final Logger LOGGER = LoggerFactory.getLogger(ApiRateLimitStatus.class); - public boolean isCritical() { return this.equals(CRITICAL); } @@ -25,21 +23,26 @@ public boolean isGoodOrWorse() { } public ApiRateLimitStatus getNextBest() { - return switch (this) { - case CRITICAL -> WARNING; - case WARNING -> CONCERNING; - case CONCERNING -> GOOD; - case GOOD, OK -> OK; - }; - } - - public boolean isBetterThen(ApiRateLimitStatus other) { - var better = this.ordinal() > other.ordinal(); - LOGGER.debug( - "The status {} is better then the status {}: {}", - this, other, better + return this.newStatus(ordinal -> ordinal + 1); + } + + public ApiRateLimitStatus getNextWorst() { + return this.newStatus(ordinal -> ordinal - 1); + } + + private ApiRateLimitStatus newStatus(IntUnaryOperator function) { + var newStatus = Math.clamp( + function.applyAsInt(this.ordinal()), min().ordinal(), max().ordinal() ); - return better; + return values()[newStatus]; + } + + public ApiRateLimitStatus max() { + return Arrays.stream(values()).max(ApiRateLimitStatus::compareTo).orElseThrow(); + } + + public ApiRateLimitStatus min() { + return Arrays.stream(values()).min(ApiRateLimitStatus::compareTo).orElseThrow(); } } diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/GetCurrentApiRateLimitStateUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/GetCurrentApiRateLimitStateUseCase.java index a81256e..f6b7648 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/GetCurrentApiRateLimitStateUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/apistate/GetCurrentApiRateLimitStateUseCase.java @@ -1,10 +1,13 @@ package be.xplore.githubmetrics.domain.apistate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class GetCurrentApiRateLimitStateUseCase { + private static final Logger LOGGER = LoggerFactory.getLogger(GetCurrentApiRateLimitStateUseCase.class); private final ApiRateLimitStateQueryPort apiRateLimitStateQueryPort; public GetCurrentApiRateLimitStateUseCase( @@ -14,6 +17,7 @@ public GetCurrentApiRateLimitStateUseCase( } public ApiRateLimitState getCurrentApiRateLimitState() { + LOGGER.debug("Exporting current Api rate limit state"); return this.apiRateLimitStateQueryPort.getCurrentApiRateLimitState(); } } diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/job/GetAllJobsOfLastDayUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/job/GetAllJobsOfLastDayUseCase.java index fd3cdf2..3a202d8 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/job/GetAllJobsOfLastDayUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/job/GetAllJobsOfLastDayUseCase.java @@ -25,7 +25,7 @@ public GetAllJobsOfLastDayUseCase( public List getAllJobsOfLastDay() { List workflowRuns = getAllWorkflowRunsOfLastDayUseCase.getAllWorkflowRunsOfLastDay(); - LOGGER.info("Exporting Jobs of {} workflow runs.", workflowRuns.size()); + LOGGER.debug("Exporting Jobs of {} workflow runs.", workflowRuns.size()); return workflowRuns.stream().map( jobsQuery::getAllJobsForWorkflowRun ).flatMap(List::stream).toList(); diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/pullrequest/GetAllPullRequestsUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/pullrequest/GetAllPullRequestsUseCase.java index 256e2b7..20382c9 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/pullrequest/GetAllPullRequestsUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/pullrequest/GetAllPullRequestsUseCase.java @@ -25,7 +25,7 @@ public GetAllPullRequestsUseCase( public List getAllPullRequests() { List repositories = repositoriesQuery.getAllRepositories(); - LOGGER.info("Exporting pull requests for {} repositories.", repositories.size()); + LOGGER.debug("Exporting pull requests for {} repositories.", repositories.size()); return repositories.stream().map( pullRequestQuery::getAllPullRequestsForRepository ).flatMap(List::stream).toList(); diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/repository/GetAllRepositoriesUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/repository/GetAllRepositoriesUseCase.java index dd7d0fb..c94a631 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/repository/GetAllRepositoriesUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/repository/GetAllRepositoriesUseCase.java @@ -17,7 +17,7 @@ public GetAllRepositoriesUseCase(RepositoriesQueryPort repositoriesQueryPort) { } public List getAllRepositories() { - LOGGER.info("Exporting all available Repositories."); + LOGGER.debug("Exporting all available Repositories."); return this.repositoriesQueryPort.getAllRepositories(); } } diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/selfhostedrunner/GetAllSelfHostedRunnersUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/selfhostedrunner/GetAllSelfHostedRunnersUseCase.java index 25bafa5..143113a 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/selfhostedrunner/GetAllSelfHostedRunnersUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/selfhostedrunner/GetAllSelfHostedRunnersUseCase.java @@ -24,7 +24,7 @@ public GetAllSelfHostedRunnersUseCase( public List getAllSelfHostedRunners() { var repositories = this.repositoriesQuery.getAllRepositories(); - LOGGER.info( + LOGGER.debug( "Exporting self hosted runners for organization and {} repositories", repositories.size() ); diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetActiveWorkflowRunsUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetActiveWorkflowRunsUseCase.java index 366ac62..3ad3312 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetActiveWorkflowRunsUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetActiveWorkflowRunsUseCase.java @@ -2,6 +2,8 @@ import be.xplore.githubmetrics.domain.repository.RepositoriesQueryPort; import be.xplore.githubmetrics.domain.repository.Repository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.List; @@ -9,6 +11,7 @@ @Component public class GetActiveWorkflowRunsUseCase { + private static final Logger LOGGER = LoggerFactory.getLogger(GetActiveWorkflowRunsUseCase.class); private final RepositoriesQueryPort repositoriesQuery; private final ActiveWorkflowRunsQueryPort activeWorkflowRunsQuery; @@ -22,6 +25,10 @@ public GetActiveWorkflowRunsUseCase( public List getActiveWorkflowRuns() { List repositories = repositoriesQuery.getAllRepositories(); + LOGGER.debug( + "Exporting active workflow runs for {} repositories.", + repositories.size() + ); return repositories.stream().map( activeWorkflowRunsQuery::getActiveWorkflowRuns ).flatMap(List::stream).toList(); diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunBuildTimesOfLastDayUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunBuildTimesOfLastDayUseCase.java index 397ce0e..fbeff3e 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunBuildTimesOfLastDayUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunBuildTimesOfLastDayUseCase.java @@ -24,7 +24,7 @@ public GetAllWorkflowRunBuildTimesOfLastDayUseCase( public List getAllWorkflowRunBuildTime() { List workflowRuns = getAllWorkflowRunsOfLastDayUseCase.getAllWorkflowRunsOfLastDay(); - LOGGER.info("Exporting build times for {} workflow runs.", workflowRuns.size()); + LOGGER.debug("Exporting build times for {} workflow runs.", workflowRuns.size()); workflowRuns.forEach(workflowRun -> workflowRun.setBuildTime( workflowRunBuildTimesQueryPort.getWorkflowRunBuildTimes(workflowRun) diff --git a/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunsOfLastDayUseCase.java b/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunsOfLastDayUseCase.java index c568803..aa97504 100644 --- a/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunsOfLastDayUseCase.java +++ b/domain/src/main/java/be/xplore/githubmetrics/domain/workflowrun/GetAllWorkflowRunsOfLastDayUseCase.java @@ -24,7 +24,7 @@ public GetAllWorkflowRunsOfLastDayUseCase( public List getAllWorkflowRunsOfLastDay() { List repositories = repositoriesQuery.getAllRepositories(); - LOGGER.info( + LOGGER.debug( "Exporting workflow runs of last day for {} repositories.", repositories.size() ); diff --git a/domain/src/test/java/be/xplore/githubmetrics/domain/ApiStateTests.java b/domain/src/test/java/be/xplore/githubmetrics/domain/ApiStateTests.java index dad52ee..4ecfac5 100644 --- a/domain/src/test/java/be/xplore/githubmetrics/domain/ApiStateTests.java +++ b/domain/src/test/java/be/xplore/githubmetrics/domain/ApiStateTests.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class ApiStateTests { @@ -29,38 +30,63 @@ private ApiRateLimitStatus getStatus() { return this.state.getStatus().get(); } + private ApiRateLimitStatus getAbsNewStatus() { + return this.state.getNewAbsoluteStatus(); + } + + @Test + void drasticRequestRatioWorseningShouldOnlyWorsenStatusByOne() { + this.state.recalculateStatus(2, 1); + assertEquals(ApiRateLimitStatus.GOOD, getStatus()); + } + + @Test + void consistentRatioWorseningShouldSlowlyIncreaseStatus() { + this.state.recalculateStatus(2, 1); + assertEquals(ApiRateLimitStatus.GOOD, getStatus()); + this.state.recalculateStatus(2, 1); + assertEquals(ApiRateLimitStatus.CONCERNING, getStatus()); + this.state.recalculateStatus(2, 1); + assertEquals(ApiRateLimitStatus.WARNING, getStatus()); + this.state.recalculateStatus(2, 1); + assertEquals(ApiRateLimitStatus.CRITICAL, getStatus()); + } + @Test void actualToIdealRatioThatDoesntExceedGoodLimitShouldStayOk() { this.state.recalculateStatus(GOOD_LIMIT, 1); - assertEquals(ApiRateLimitStatus.OK, getStatus()); + assertEquals(ApiRateLimitStatus.OK, getAbsNewStatus()); } @Test void actualToIdealRatioThatDoesntExceedConcerningLimitShouldBecomeOk() { this.state.recalculateStatus(CONCERNING_LIMIT, 1); - assertEquals(ApiRateLimitStatus.GOOD, getStatus()); + assertEquals(ApiRateLimitStatus.GOOD, getAbsNewStatus()); } @Test void actualToIdealRatioThatDoesntExceedWarningLimitShouldBecomeConcerning() { this.state.recalculateStatus(WARNING_LIMIT, 1); - assertEquals(ApiRateLimitStatus.CONCERNING, getStatus()); + assertEquals(ApiRateLimitStatus.CONCERNING, getAbsNewStatus()); } @Test void actualToIdealRatioThatDoesntExceedCriticalLimitShouldBecomeConcerning() { this.state.recalculateStatus(CRITICAL_LIMIT, 1); - assertEquals(ApiRateLimitStatus.WARNING, getStatus()); + assertEquals(ApiRateLimitStatus.WARNING, getAbsNewStatus()); } @Test void actualToIdealRatioThatExceedsCriticalLimitShouldBecomeCritical() { this.state.recalculateStatus(2, 1); - assertEquals(ApiRateLimitStatus.CRITICAL, getStatus()); + assertEquals(ApiRateLimitStatus.CRITICAL, getAbsNewStatus()); } @Test void drasticRequestRatioImprovementShouldOnlyImproveStatusByOne() { + this.state.recalculateStatus(2, 1); + this.state.recalculateStatus(2, 1); + this.state.recalculateStatus(2, 1); this.state.recalculateStatus(2, 1); assertEquals(ApiRateLimitStatus.CRITICAL, getStatus()); this.state.recalculateStatus(GOOD_LIMIT, 1); @@ -75,4 +101,43 @@ void stopRequestsAndGetResetFunctionShouldClearStatusAndAllowForReset() { resetFn.run(); assertEquals(ApiRateLimitStatus.CRITICAL, getStatus()); } + + @Test + void completelyStoppingRequestingShouldAlwaysCauseDataWaitToReturnTrue() { + this.state.stopRequestsAndGetResetFunction(); + + shouldWaitAndRaise(ApiRateLimitStatus.GOOD); + shouldWaitAndRaise(ApiRateLimitStatus.CONCERNING); + shouldWaitAndRaise(ApiRateLimitStatus.WARNING); + shouldWaitAndRaise(ApiRateLimitStatus.CONCERNING); + shouldWaitAndRaise(null); + } + + @Test + void dataShouldWaitWhenActualStatusIsOnSameLevelAsLimit() { + shouldWaitAndRaise(ApiRateLimitStatus.OK); + shouldWaitAndRaise(ApiRateLimitStatus.GOOD); + shouldWaitAndRaise(ApiRateLimitStatus.CONCERNING); + shouldWaitAndRaise(ApiRateLimitStatus.WARNING); + shouldWaitAndRaise(ApiRateLimitStatus.CRITICAL); + } + + @Test + void nullLimitShouldNeverAskDataToWait() { + shouldNotWaitAndRaise(null); + shouldNotWaitAndRaise(null); + shouldNotWaitAndRaise(null); + shouldNotWaitAndRaise(null); + shouldNotWaitAndRaise(null); + } + + private void shouldNotWaitAndRaise(ApiRateLimitStatus status) { + assertFalse(this.state.shouldDataWait(status)); + this.state.raiseStatusByOne(); + } + + private void shouldWaitAndRaise(ApiRateLimitStatus status) { + assertTrue(this.state.shouldDataWait(status)); + this.state.raiseStatusByOne(); + } } \ No newline at end of file diff --git a/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/cacheevicting/EvictionScheduler.java b/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/cacheevicting/EvictionScheduler.java index 72e606e..d69187a 100644 --- a/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/cacheevicting/EvictionScheduler.java +++ b/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/cacheevicting/EvictionScheduler.java @@ -4,7 +4,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.support.CronTrigger; @@ -48,12 +47,14 @@ private void conditionallyCacheEvict(ScheduledCacheEvictionPort port) { var name = port.cacheName(); if (!port.freshDataCanWait()) { - LOGGER.debug("Evicting {}", name); Optional.ofNullable( this.cacheManager.getCache(name) - ).ifPresent(Cache::clear); + ).ifPresent(cache -> { + LOGGER.info("Evicting the cache with name: {}", name); + cache.clear(); + }); } else { - LOGGER.debug("Did not evict {}", name); + LOGGER.info("Did not evict the cache with name: {}", name); } } diff --git a/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitStateControlScheduler.java b/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitStateControlScheduler.java index a97d625..e36d8f6 100644 --- a/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitStateControlScheduler.java +++ b/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitStateControlScheduler.java @@ -26,7 +26,7 @@ public RateLimitStateControlScheduler( String controlSchedule = githubProperties.ratelimiting().stateControlCheckSchedule(); - LOGGER.error("Rate-limit state control scheduler is on schedule {}.", controlSchedule); + LOGGER.info("Rate-limit state control scheduler is on schedule {}.", controlSchedule); taskScheduler.schedule( this::controlApiRateLimitState, @@ -36,9 +36,19 @@ public RateLimitStateControlScheduler( } private void controlApiRateLimitState() { + LOGGER.info( + "Rate limit control scheduler ran with previous used {} and current used {}.", + this.lastRemainingRequests, + this.rateLimitState.getUsed() + ); if (this.rateLimitState.getUsed() == this.lastRemainingRequests) { + var previousStatus = this.rateLimitState.getStatus(); this.rateLimitState.lowerStatusByOne(); + LOGGER.debug( + "Since previous used and current used are not equal status will be lowered from {} to {}.", + previousStatus, this.rateLimitState.getStatus() + ); } - this.lastRemainingRequests = rateLimitState.getUsed(); + this.lastRemainingRequests = this.rateLimitState.getUsed(); } } diff --git a/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitingInterceptor.java b/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitingInterceptor.java index 4de6a43..d2a04e9 100644 --- a/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitingInterceptor.java +++ b/github-adapter/src/main/java/be/xplore/githubmetrics/githubadapter/config/ratelimiting/RateLimitingInterceptor.java @@ -195,7 +195,7 @@ private ZonedDateTime now() { } private void logRateLimitStateBefore() { - LOGGER.info("State before is {}", this.rateLimitState.getStatus()); + LOGGER.debug("State before is {}", this.rateLimitState.getStatus()); LOGGER.trace( "Rate limit used requests was {} and now is {}", usedRequestsAtCountingStart.get(), this.rateLimitState.getUsed() @@ -218,6 +218,6 @@ private void logRateLimitStateAfter(double numRequestsInDuration) { "Remaining minutes until limit reset {}", ChronoUnit.MINUTES.between(now(), this.rateLimitState.getReset(now().getOffset())) ); - LOGGER.info("State after is {}", this.rateLimitState.getStatus()); + LOGGER.debug("State after is {}", this.rateLimitState.getStatus()); } } \ No newline at end of file diff --git a/prometheus-exporter/src/main/java/be/xplore/githubmetrics/prometheusexporter/features/Features.java b/prometheus-exporter/src/main/java/be/xplore/githubmetrics/prometheusexporter/features/Features.java index 3476c70..ba7965a 100644 --- a/prometheus-exporter/src/main/java/be/xplore/githubmetrics/prometheusexporter/features/Features.java +++ b/prometheus-exporter/src/main/java/be/xplore/githubmetrics/prometheusexporter/features/Features.java @@ -1,10 +1,13 @@ package be.xplore.githubmetrics.prometheusexporter.features; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.togglz.core.Feature; import org.togglz.core.context.FeatureContext; public enum Features implements Feature { + EXPORTER_JOBS_FEATURE, EXPORTER_WORKFLOW_RUNS_FEATURE, EXPORTER_ACTIVE_WORKFLOW_RUNS_FEATURE, @@ -13,6 +16,12 @@ public enum Features implements Feature { EXPORTER_REPOSITORIES_FEATURE, EXPORTER_PULL_REQUESTS_FEATURE; + private final Logger logger = LoggerFactory.getLogger(Features.class); + + Features() { + logger.info("Feature {} is active.", this); + } + @Override public boolean isActive() { return FeatureContext.getFeatureManager().isActive(this);