class RetryUtil {

    private static final Set<Class<? extends Throwable>> KNOWN_TRANSIENT_EXCEPTIONS = Set.of(
            ConnectTimeoutException.class,
            SocketTimeoutException.class,
            TimeoutException.class
    );
    private static final Set<Integer> KNOWN_TRANSIENT_STATUS_CODES = Set.of(
            HttpStatus.SC_TOO_MANY_REQUESTS,
            HttpStatus.SC_BAD_GATEWAY,
            HttpStatus.SC_SERVICE_UNAVAILABLE,
            HttpStatus.SC_GATEWAY_TIMEOUT
    );

    public static IntervalFunction withExponentialBackoff(final ConfigKey initialDurationConfigKey,
                                                          final ConfigKey multiplierConfigKey,
                                                          final ConfigKey maxDurationConfigKey) {
        return IntervalFunction.ofExponentialBackoff(
                Duration.ofMillis(Config.getInstance().getPropertyAsInt(initialDurationConfigKey)),
                Config.getInstance().getPropertyAsInt(multiplierConfigKey),
                Duration.ofMillis(Config.getInstance().getPropertyAsInt(maxDurationConfigKey))
        );
    }

    public static <T extends RetryEvent> EventConsumer<T> logRetryEventWith(final Logger logger) {<FILL_FUNCTION_BODY>}

    public static <T extends Closeable> BiConsumer<Integer, T> maybeClosePreviousResult() {
        return (attempt, closeable) -> {
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };
    }

    public static Predicate<CloseableHttpResponse> withTransientErrorCode() {
        return response -> response != null
                           && response.getStatusLine() != null
                           && KNOWN_TRANSIENT_STATUS_CODES.contains(response.getStatusLine().getStatusCode());
    }

    public static Predicate<Throwable> withTransientCause() {
        return withCauseAnyOf(KNOWN_TRANSIENT_EXCEPTIONS);
    }

    public static Predicate<Throwable> withCauseAnyOf(final Collection<Class<? extends Throwable>> causeClasses) {
        return throwable -> {
            for (final Throwable cause : ExceptionUtils.getThrowableList(throwable)) {
                final boolean isMatch = causeClasses.stream()
                        .anyMatch(causeClass -> causeClass.isAssignableFrom(cause.getClass()));
                if (isMatch) {
                    return true;
                }
            }

            return false;
        };
    }

}

        return event -> {
            if (event instanceof final RetryOnRetryEvent retryEvent) {
                final var message = "Encountered retryable error for %s; Will execute retry #%d in %s"
                        .formatted(event.getName(), event.getNumberOfRetryAttempts(), retryEvent.getWaitInterval());
                if (event.getLastThrowable() != null) {
                    logger.warn(message, event.getLastThrowable());
                } else {
                    logger.warn(message);
                }
            } else if (event instanceof final RetryOnErrorEvent errorEvent) {
                final var message = "Max retry attempts exceeded for %s after %d attempts"
                        .formatted(errorEvent.getName(), errorEvent.getNumberOfRetryAttempts());
                if (errorEvent.getLastThrowable() != null) {
                    logger.error(message, errorEvent.getLastThrowable());
                } else {
                    logger.error(message);
                }
            } else if (event instanceof final RetryOnIgnoredErrorEvent ignoredErrorEvent) {
                if (!logger.isDebugEnabled()) {
                    return;
                }

                final var message = "Ignored error for %s after %d attempts; Will not retry"
                        .formatted(event.getName(), event.getNumberOfRetryAttempts());
                if (event.getLastThrowable() != null) {
                    logger.debug(message, event.getLastThrowable());
                } else {
                    logger.debug(message);
                }
            }
        };
    
<no_super_class>