Skip to content

Add rate-limit observability events#3030

Open
Eministar wants to merge 2 commits intodiscord-jda:masterfrom
Eministar:master
Open

Add rate-limit observability events#3030
Eministar wants to merge 2 commits intodiscord-jda:masterfrom
Eministar:master

Conversation

@Eministar
Copy link

@Eministar Eministar commented Feb 19, 2026

Pull Request Etiquette

  • I have checked the PRs for upcoming features/bug fixes.
  • I have read the contributing guidelines.
  • I applied the code formatter to my changes with ./gradlew format

Changes

  • Internal code
  • Library interface (affecting end-user code)
  • Documentation
  • Other: _____

Closes Issue: NaN

Description

This PR adds a structured observability hook for JDA rate-limit processing, focused on the default SequentialRestRateLimiter.

What was added

  1. Public event model for rate-limit telemetry
  • Added RestRateLimiter.RateLimitEvent with typed event categories:
    • REQUEST_QUEUED
    • REQUEST_REQUEUED
    • BUCKET_UPDATED
    • RATE_LIMITED
    • BACKOFF
  • Added event metadata for route/bucket/queue state/retry timing/scope.
  1. Configurable event consumer in RestConfig
  • Added setRateLimitEventConsumer(...)
  • Added getRateLimitEventConsumer()
  1. Wiring into runtime initialization
  • JDAImpl now passes the configured event consumer into RestRateLimiter.RateLimitConfig.
  1. Emission in default limiter
  • SequentialRestRateLimiter now emits structured events during queueing, retries, bucket updates, 429 handling, and backoff windows.
  1. Documentation
  • Added Javadocs for all new public API types/methods.
  • Added README section with usage example (Rate-Limit Observability).

Usage example

RestConfig restConfig = new RestConfig().setRateLimitEventConsumer(event -> {
    if (event.getType() == RestRateLimiter.RateLimitEvent.Type.RATE_LIMITED) {
        int shardId = event.getJDA().getShardInfo() == null
                ? -1
                : event.getJDA().getShardInfo().getShardId();

        System.out.printf(
                "rate-limit shard=%d route=%s bucket=%s retryAfterMs=%d scope=%s%n",
                shardId,
                event.getRoute().getBaseRoute(),
                event.getBucketId(),
                event.getRetryAfterMillis(),
                event.getScope());
    }
});

JDABuilder.createDefault(BOT_TOKEN)
    .setRestConfig(restConfig)
    .build();  

Scope

This is one logical feature: observability for REST rate-limit handling.
No unrelated refactors are included.

Validation

  • Ran ./gradlew format
  • Ran ./gradlew build
  • Build passed successfully

  This change introduces a neutral metrics integration point for JDA REST
  processing and wires it into both request execution and rate-limit handling.

  Motivation
  - Large bots need stable operational visibility for REST throughput,
    failures, retries, and rate-limit pressure.
  - Existing logging is useful for debugging, but not ideal for metrics
    backends and alerting pipelines.
  - A lightweight, backend-agnostic collector API provides a clean bridge
    to Micrometer/OpenTelemetry adapters without introducing hard dependencies.

  What was added
  - New public API type: RestMetricsCollector
    - onRequest(RequestMetric): request execution metrics
    - onRateLimit(RateLimitEvent): rate-limit and backoff metrics
  - New request metric model:
    - JDA instance
    - compiled route
    - HTTP status code (-1 when no HTTP response exists)
    - attempt count
    - duration in milliseconds
    - success flag
    - queued flag
    - execution error (nullable)
  - RestConfig support:
    - setMetricsCollector(RestMetricsCollector)
    - getMetricsCollector()
  - RateLimitConfig support:
    - optional metricsCollector field and accessor
    - constructor wiring for metrics collector propagation
  - Runtime wiring in JDAImpl:
    - configured RestConfig metrics collector is passed into RateLimitConfig
  - SequentialRestRateLimiter integration:
    - existing rate-limit events are now forwarded to
      RestMetricsCollector#onRateLimit when configured
    - callback failures are isolated and logged (non-fatal)
  - Requester integration:
    - emits RequestMetric for successful responses, retry-exhausted paths,
      skipped requests, and transport exceptions
    - tracks attempts and end-to-end duration
    - callback failures are isolated and logged (non-fatal)

  Documentation
  - Added README “Metrics Bridge” section with usage example.
  - Added/updated Javadocs for all new public API methods and types.

  Compatibility and behavior
  - Backward compatible.
  - No default runtime behavior change when no collector is configured.
  - No backend dependency added (adapter responsibility remains external).
  - Collector callbacks run on request/rate-limit worker threads and should be
    non-blocking.

  Validation
  - Ran ./gradlew format
  - Ran ./gradlew build
  - Build and tests passed successfully.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants