2626import com .google .auto .service .AutoService ;
2727import io .opentelemetry .api .common .Attributes ;
2828import io .opentelemetry .exporter .otlp .http .metrics .OtlpHttpMetricExporter ;
29- import io .opentelemetry .exporter .otlp .http .metrics .OtlpHttpMetricExporterBuilder ;
3029import io .opentelemetry .exporter .otlp .http .trace .OtlpHttpSpanExporter ;
31- import io .opentelemetry .exporter .otlp .http .trace .OtlpHttpSpanExporterBuilder ;
3230import io .opentelemetry .exporter .otlp .metrics .OtlpGrpcMetricExporter ;
33- import io .opentelemetry .exporter .otlp .metrics .OtlpGrpcMetricExporterBuilder ;
3431import io .opentelemetry .exporter .otlp .trace .OtlpGrpcSpanExporter ;
35- import io .opentelemetry .exporter .otlp .trace .OtlpGrpcSpanExporterBuilder ;
3632import io .opentelemetry .sdk .autoconfigure .spi .AutoConfigurationCustomizer ;
3733import io .opentelemetry .sdk .autoconfigure .spi .AutoConfigurationCustomizerProvider ;
3834import io .opentelemetry .sdk .autoconfigure .spi .ConfigProperties ;
4541import java .util .Map ;
4642import java .util .Objects ;
4743import java .util .Optional ;
48- import java .util .logging .Level ;
49- import java .util .logging .Logger ;
5044import javax .annotation .Nonnull ;
45+ import javax .annotation .Nullable ;
5146import org .apache .beam .sdk .extensions .opentelemetry .gcp .auth .GoogleAuthException .Reason ;
5247
5348/**
7065public class GcpAuthAutoConfigurationCustomizerProvider
7166 implements AutoConfigurationCustomizerProvider {
7267
73- private static final Logger logger =
74- Logger .getLogger (GcpAuthAutoConfigurationCustomizerProvider .class .getName ());
75- private static final String SIGNAL_TARGET_WARNING_FIX_SUGGESTION =
76- String .format (
77- "You may safely ignore this warning if it is intentional, otherwise please configure the '%s' by exporting valid values to environment variable: %s or by setting valid values in system property: %s." ,
78- ConfigurableOption .GOOGLE_OTEL_AUTH_TARGET_SIGNALS .getUserReadableName (),
79- ConfigurableOption .GOOGLE_OTEL_AUTH_TARGET_SIGNALS .getEnvironmentVariable (),
80- ConfigurableOption .GOOGLE_OTEL_AUTH_TARGET_SIGNALS .getSystemProperty ());
81-
8268 static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project" ;
8369 static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id" ;
8470
8571 static final String SIGNAL_TYPE_TRACES = "traces" ;
8672 static final String SIGNAL_TYPE_METRICS = "metrics" ;
8773 static final String SIGNAL_TYPE_ALL = "all" ;
8874
75+ private @ Nullable GoogleCredentials credentials ;
76+
8977 /**
9078 * Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to
9179 * GCP Telemetry API are possible from the configured OTLP exporter.
9280 *
93- * <p>This method attempts to retrieve Google Application Default Credentials (ADC) and performs
94- * the following:
81+ * <p>This method performs the following:
9582 *
9683 * <ul>
9784 * <li>Verifies whether the configured OTLP endpoint (base or signal specific) is a known GCP
@@ -107,60 +94,43 @@ public class GcpAuthAutoConfigurationCustomizerProvider
10794 * enable GCP integration.
10895 *
10996 * @param autoConfiguration the AutoConfigurationCustomizer to customize.
110- * @throws GoogleAuthException if there's an error retrieving Google Application Default
111- * Credentials.
112- * @throws io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException if required options are
113- * not configured through environment variables or system properties.
11497 */
11598 @ Override
11699 public void customize (@ Nonnull AutoConfigurationCustomizer autoConfiguration ) {
117- GoogleCredentials credentials ;
118- try {
119- credentials = GoogleCredentials .getApplicationDefault ();
120- } catch (IOException e ) {
121- throw new GoogleAuthException (Reason .FAILED_ADC_RETRIEVAL , e );
122- }
123100 autoConfiguration
124- .addSpanExporterCustomizer (
125- (spanExporter , configProperties ) ->
126- customizeSpanExporter (spanExporter , credentials , configProperties ))
127- .addMetricExporterCustomizer (
128- (metricExporter , configProperties ) ->
129- customizeMetricExporter (metricExporter , credentials , configProperties ))
130- .addResourceCustomizer (
131- (resource , configProperties ) ->
132- customizeResource (resource , credentials , configProperties ));
101+ .addSpanExporterCustomizer (this ::customizeSpanExporter )
102+ .addMetricExporterCustomizer (this ::customizeMetricExporter )
103+ .addResourceCustomizer (this ::customizeResource );
133104 }
134105
135106 @ Override
136107 public int order () {
137108 return Integer .MAX_VALUE - 1 ;
138109 }
139110
140- private static SpanExporter customizeSpanExporter (
141- SpanExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
111+ private synchronized GoogleCredentials getCredentials () {
112+ if (credentials == null ) {
113+ try {
114+ credentials = GoogleCredentials .getApplicationDefault ();
115+ } catch (IOException e ) {
116+ throw new GoogleAuthException (Reason .FAILED_ADC_RETRIEVAL , e );
117+ }
118+ }
119+ return credentials ;
120+ }
121+
122+ private SpanExporter customizeSpanExporter (
123+ SpanExporter exporter , ConfigProperties configProperties ) {
142124 if (isSignalTargeted (SIGNAL_TYPE_TRACES , configProperties )) {
143- return addAuthorizationHeaders (exporter , credentials , configProperties );
144- } else {
145- String [] params = {SIGNAL_TYPE_TRACES , SIGNAL_TARGET_WARNING_FIX_SUGGESTION };
146- logger .log (
147- Level .WARNING ,
148- "GCP Authentication Extension is not configured for signal type: {0}. {1}" ,
149- params );
125+ return addAuthorizationHeaders (exporter , configProperties );
150126 }
151127 return exporter ;
152128 }
153129
154- private static MetricExporter customizeMetricExporter (
155- MetricExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
130+ private MetricExporter customizeMetricExporter (
131+ MetricExporter exporter , ConfigProperties configProperties ) {
156132 if (isSignalTargeted (SIGNAL_TYPE_METRICS , configProperties )) {
157- return addAuthorizationHeaders (exporter , credentials , configProperties );
158- } else {
159- String [] params = {SIGNAL_TYPE_METRICS , SIGNAL_TARGET_WARNING_FIX_SUGGESTION };
160- logger .log (
161- Level .WARNING ,
162- "GCP Authentication Extension is not configured for signal type: {0}. {1}" ,
163- params );
133+ return addAuthorizationHeaders (exporter , configProperties );
164134 }
165135 return exporter ;
166136 }
@@ -171,11 +141,25 @@ private static boolean isSignalTargeted(String checkSignal, ConfigProperties con
171141 if (endpoint == null ) {
172142 endpoint = configProperties .getString ("otel.exporter.otlp.endpoint" );
173143 }
174- if (endpoint == null
175- || (!endpoint .startsWith ("https://telemetry.googleapis.com" )
176- && !endpoint .startsWith ("https://telemetry.mtls.googleapis.com" ))) {
144+ if (endpoint == null ) {
177145 return false ;
178146 }
147+
148+ try {
149+ java .net .URI uri = new java .net .URI (endpoint );
150+ String host = uri .getHost ();
151+ String scheme = uri .getScheme ();
152+ if (host == null
153+ || scheme == null
154+ || !scheme .equalsIgnoreCase ("https" )
155+ || (!host .equalsIgnoreCase ("telemetry.googleapis.com" )
156+ && !host .equalsIgnoreCase ("telemetry.mtls.googleapis.com" ))) {
157+ return false ;
158+ }
159+ } catch (java .net .URISyntaxException e ) {
160+ return false ;
161+ }
162+
179163 String userSpecifiedTargetedSignals =
180164 ConfigurableOption .GOOGLE_OTEL_AUTH_TARGET_SIGNALS .getConfiguredValueWithFallback (
181165 configProperties , () -> SIGNAL_TYPE_ALL );
@@ -186,57 +170,74 @@ private static boolean isSignalTargeted(String checkSignal, ConfigProperties con
186170 targetedSignal .equals (checkSignal ) || targetedSignal .equals (SIGNAL_TYPE_ALL ));
187171 }
188172
173+ private boolean isAnySignalTargeted (ConfigProperties configProperties ) {
174+ return isSignalTargeted (SIGNAL_TYPE_TRACES , configProperties )
175+ || isSignalTargeted (SIGNAL_TYPE_METRICS , configProperties );
176+ }
177+
189178 // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and
190179 // OtlpHttpSpanExporter.
191- private static SpanExporter addAuthorizationHeaders (
192- SpanExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
180+ private SpanExporter addAuthorizationHeaders (
181+ SpanExporter exporter , ConfigProperties configProperties ) {
193182 if (exporter instanceof OtlpHttpSpanExporter ) {
194- OtlpHttpSpanExporterBuilder builder =
183+ SpanExporter result =
195184 ((OtlpHttpSpanExporter ) exporter )
196185 .toBuilder ()
197- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
198- return builder .build ();
186+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
187+ .build ();
188+ exporter .shutdown ();
189+ return result ;
199190 } else if (exporter instanceof OtlpGrpcSpanExporter ) {
200- OtlpGrpcSpanExporterBuilder builder =
191+ SpanExporter result =
201192 ((OtlpGrpcSpanExporter ) exporter )
202193 .toBuilder ()
203- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
204- return builder .build ();
194+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
195+ .build ();
196+ exporter .shutdown ();
197+ return result ;
205198 }
206199 return exporter ;
207200 }
208201
209202 // Adds authorization headers to the calls made by the OtlpGrpcMetricExporter and
210203 // OtlpHttpMetricExporter.
211- private static MetricExporter addAuthorizationHeaders (
212- MetricExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
204+ private MetricExporter addAuthorizationHeaders (
205+ MetricExporter exporter , ConfigProperties configProperties ) {
213206 if (exporter instanceof OtlpHttpMetricExporter ) {
214- OtlpHttpMetricExporterBuilder builder =
207+ MetricExporter result =
215208 ((OtlpHttpMetricExporter ) exporter )
216209 .toBuilder ()
217- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
218- return builder .build ();
210+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
211+ .build ();
212+ exporter .shutdown ();
213+ return result ;
219214 } else if (exporter instanceof OtlpGrpcMetricExporter ) {
220- OtlpGrpcMetricExporterBuilder builder =
215+ MetricExporter result =
221216 ((OtlpGrpcMetricExporter ) exporter )
222217 .toBuilder ()
223- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
224- return builder .build ();
218+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
219+ .build ();
220+ exporter .shutdown ();
221+ return result ;
225222 }
226223 return exporter ;
227224 }
228225
229- private static Map <String , String > getRequiredHeaderMap (
230- GoogleCredentials credentials , ConfigProperties configProperties ) {
226+ private Map <String , String > getRequiredHeaderMap (ConfigProperties configProperties ) {
227+ GoogleCredentials creds = getCredentials ();
231228 Map <String , List <String >> gcpHeaders ;
232229 try {
233230 // this also refreshes the credentials, if required
234- gcpHeaders = credentials .getRequestMetadata ();
231+ gcpHeaders = creds .getRequestMetadata ();
235232 } catch (IOException e ) {
236233 throw new GoogleAuthException (Reason .FAILED_ADC_REFRESH , e );
237234 }
235+ if (gcpHeaders == null ) {
236+ return Map .of ();
237+ }
238238 Map <String , String > flattenedHeaders =
239239 gcpHeaders .entrySet ().stream ()
240+ .filter (entry -> entry .getKey () != null && entry .getValue () != null )
240241 .collect (
241242 toMap (
242243 Map .Entry ::getKey ,
@@ -259,13 +260,16 @@ private static Map<String, String> getRequiredHeaderMap(
259260 }
260261
261262 // Updates the current resource with the attributes required for ingesting OTLP data on GCP.
262- private static Resource customizeResource (
263- Resource resource , GoogleCredentials credentials , ConfigProperties configProperties ) {
263+ private Resource customizeResource (Resource resource , ConfigProperties configProperties ) {
264+ if (!isAnySignalTargeted (configProperties )) {
265+ return resource ;
266+ }
267+
264268 String gcpProjectId ;
265269 try {
266270 gcpProjectId = ConfigurableOption .GOOGLE_CLOUD_PROJECT .getConfiguredValue (configProperties );
267271 } catch (ConfigurationException e ) {
268- gcpProjectId = credentials .getProjectId ();
272+ gcpProjectId = getCredentials () .getProjectId ();
269273 if (gcpProjectId == null || gcpProjectId .isEmpty ()) {
270274 throw e ;
271275 }
0 commit comments