From 0781c78de48536bdd07d358f1b4a0194035a1526 Mon Sep 17 00:00:00 2001 From: fosky94 Date: Mon, 26 Oct 2026 16:44:07 +0000 Subject: [PATCH] feat(memorystore): migrate Redis client-side metrics sample to OTLP --- memorystore/redis/client_side_metrics/pom.xml | 28 ++-- .../memorystore/redis/RedisTelemetryApp.java | 134 ++++++++++++++---- 2 files changed, 122 insertions(+), 40 deletions(-) diff --git a/memorystore/redis/client_side_metrics/pom.xml b/memorystore/redis/client_side_metrics/pom.xml index 876aa034f65..686d8d49319 100644 --- a/memorystore/redis/client_side_metrics/pom.xml +++ b/memorystore/redis/client_side_metrics/pom.xml @@ -39,22 +39,32 @@ io.opentelemetry opentelemetry-api - 1.36.0 + 1.44.1 io.opentelemetry opentelemetry-sdk - 1.36.0 + 1.44.1 - com.google.cloud.opentelemetry - exporter-trace - 0.28.0 + io.opentelemetry + opentelemetry-exporter-otlp + 1.44.1 + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure-spi + 1.44.1 + + + io.opentelemetry.contrib + opentelemetry-gcp-resources + 1.37.0-alpha - com.google.cloud.opentelemetry - exporter-metrics - 0.28.0 + com.google.auth + google-auth-library-oauth2-http + 1.24.0 @@ -97,4 +107,4 @@ - \ No newline at end of file + diff --git a/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/RedisTelemetryApp.java b/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/RedisTelemetryApp.java index b9de30e53ac..49064f87847 100644 --- a/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/RedisTelemetryApp.java +++ b/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/RedisTelemetryApp.java @@ -17,8 +17,7 @@ package com.example.memorystore.redis; // [START memorystore_redis_client_side_metrics] -import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter; -import com.google.cloud.opentelemetry.trace.TraceExporter; +import com.google.auth.oauth2.GoogleCredentials; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -27,10 +26,13 @@ import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -39,18 +41,44 @@ import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.exceptions.JedisConnectionException; +import java.io.IOException; import java.time.Duration; +import java.util.Collections; +import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; /** * Sample application demonstrating client-side metrics and tracing for - * Google Cloud Memorystore for Redis. + * Google Cloud Memorystore for Redis using OpenTelemetry OTLP exporters. */ public final class RedisTelemetryApp { /** Attribute key for Redis operation names. */ private static final AttributeKey ATTR_OPERATION = AttributeKey.stringKey("operation"); + /** Attribute key for the detected cloud account/project ID. */ + private static final AttributeKey ATTR_CLOUD_ACCOUNT_ID = + AttributeKey.stringKey("cloud.account.id"); + + /** Attribute key required by the Google Cloud Telemetry API. */ + private static final AttributeKey ATTR_GCP_PROJECT_ID = + AttributeKey.stringKey("gcp.project_id"); + + /** Google Cloud Telemetry API OTLP endpoint. */ + private static final String TELEMETRY_ENDPOINT = + "https://telemetry.googleapis.com:443"; + + /** OAuth2 scope for Google Cloud APIs. */ + private static final String CLOUD_PLATFORM_SCOPE = + "https://www.googleapis.com/auth/cloud-platform"; + + /** HTTP/gRPC Authorization header name. */ + private static final String AUTHORIZATION_HEADER = "Authorization"; + + /** Prefix for OAuth2 Bearer tokens. */ + private static final String BEARER_PREFIX = "Bearer "; + /** Maximum number of Redis reconnection attempts. */ private static final int MAX_RETRIES = 3; @@ -69,6 +97,9 @@ public final class RedisTelemetryApp { /** Default Redis port. */ private static final int DEFAULT_REDIS_PORT = 6379; + /** OpenTelemetry SDK instance used for flushing telemetry on shutdown. */ + private static OpenTelemetrySdk openTelemetrySdk; + /** OpenTelemetry Tracer instance for recording trace spans. */ private static Tracer tracer; @@ -100,8 +131,9 @@ private RedisTelemetryApp() { * Main entry point for running the sample application. * * @param args Command line arguments (not used). + * @throws IOException If Google Cloud credentials cannot be loaded. */ - public static void main(final String[] args) { + public static void main(final String[] args) throws IOException { setupTelemetry(); final String host = System.getenv() @@ -121,6 +153,9 @@ public static void main(final String[] args) { if (jedisPool != null) { jedisPool.close(); } + if (openTelemetrySdk != null) { + openTelemetrySdk.close(); + } } } @@ -151,7 +186,7 @@ static String run() { /** * Injects mocked or no-op telemetry and pool instances for unit testing. * - * @param pool The mocked or test JedisPool instance. + * @param pool The mocked or test JedisPool instance. * @param testOpenTelemetry The OpenTelemetry instance to use for testing. */ static void initForTest( @@ -180,36 +215,73 @@ static void initForTest( /** * Configures the production OpenTelemetry SDK to export Traces and Metrics - * to Google Cloud Operations. + * via OTLP to Google Cloud. + * + * @throws IOException If Google Application Default Credentials fail. */ - private static void setupTelemetry() { - final SpanExporter traceExporter = - TraceExporter.createWithDefaultConfiguration(); - final SdkTracerProvider tracerProvider = - SdkTracerProvider.builder() - .addSpanProcessor( - BatchSpanProcessor.builder(traceExporter) - .build()) - .build(); - - final MetricExporter metricExporter = - GoogleCloudMetricExporter.createWithDefaultConfiguration(); - final SdkMeterProvider meterProvider = - SdkMeterProvider.builder() - .registerMetricReader( - PeriodicMetricReader.builder(metricExporter) - .setInterval(Duration.ofSeconds( - METRIC_INTERVAL_SECONDS)) - .build()) - .build(); - - final OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + private static void setupTelemetry() throws IOException { + final GoogleCredentials credentials = GoogleCredentials + .getApplicationDefault() + .createScoped(Collections.singletonList(CLOUD_PLATFORM_SCOPE)); + credentials.refreshIfExpired(); + + final Supplier> headerSupplier = () -> { + try { + synchronized (credentials) { + credentials.refreshIfExpired(); + return Collections.singletonMap( + AUTHORIZATION_HEADER, + BEARER_PREFIX + + credentials.getAccessToken() + .getTokenValue()); + } + } catch (IOException e) { + throw new RuntimeException( + "Failed to refresh Google credentials", e); + } + }; + + final Resource defaultResource = Resource.getDefault(); + String projectId = + defaultResource.getAttributes().get(ATTR_CLOUD_ACCOUNT_ID); + if (projectId == null || projectId.isEmpty()) { + projectId = System.getenv("GOOGLE_CLOUD_PROJECT"); + } + final Resource resource = projectId != null && !projectId.isEmpty() + ? defaultResource.merge(Resource.create( + Attributes.of(ATTR_GCP_PROJECT_ID, projectId))) + : defaultResource; + + final SpanExporter traceExporter = OtlpGrpcSpanExporter.builder() + .setEndpoint(TELEMETRY_ENDPOINT) + .setHeaders(headerSupplier) + .build(); + final SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + .setResource(resource) + .addSpanProcessor( + BatchSpanProcessor.builder(traceExporter).build()) + .build(); + + final MetricExporter metricExporter = OtlpGrpcMetricExporter.builder() + .setEndpoint(TELEMETRY_ENDPOINT) + .setHeaders(headerSupplier) + .build(); + final SdkMeterProvider meterProvider = SdkMeterProvider.builder() + .setResource(resource) + .registerMetricReader( + PeriodicMetricReader.builder(metricExporter) + .setInterval(Duration.ofSeconds( + METRIC_INTERVAL_SECONDS)) + .build()) + .build(); + + openTelemetrySdk = OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider) .setMeterProvider(meterProvider) .buildAndRegisterGlobal(); - tracer = openTelemetry.getTracer("jedis.client"); - final Meter meter = openTelemetry.getMeter("jedis.metrics"); + tracer = openTelemetrySdk.getTracer("jedis.client"); + final Meter meter = openTelemetrySdk.getMeter("jedis.metrics"); rttHist = meter.histogramBuilder("redis_client_rtt") .setUnit("ms").build();