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 ;
46+ import org .apache .beam .sdk .annotations .Internal ;
5147import org .apache .beam .sdk .extensions .opentelemetry .gcp .auth .GoogleAuthException .Reason ;
5248
5349/**
6763 * @see GoogleCredentials
6864 */
6965@ AutoService (AutoConfigurationCustomizerProvider .class )
66+ @ Internal
7067public class GcpAuthAutoConfigurationCustomizerProvider
7168 implements AutoConfigurationCustomizerProvider {
7269
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-
8270 static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project" ;
8371 static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id" ;
8472
8573 static final String SIGNAL_TYPE_TRACES = "traces" ;
8674 static final String SIGNAL_TYPE_METRICS = "metrics" ;
8775 static final String SIGNAL_TYPE_ALL = "all" ;
8876
77+ private @ Nullable GoogleCredentials credentials ;
78+
8979 /**
9080 * Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to
9181 * GCP Telemetry API are possible from the configured OTLP exporter.
9282 *
93- * <p>This method attempts to retrieve Google Application Default Credentials (ADC) and performs
94- * the following:
83+ * <p>This method performs the following:
9584 *
9685 * <ul>
9786 * <li>Verifies whether the configured OTLP endpoint (base or signal specific) is a known GCP
@@ -107,60 +96,43 @@ public class GcpAuthAutoConfigurationCustomizerProvider
10796 * enable GCP integration.
10897 *
10998 * @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.
11499 */
115100 @ Override
116101 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- }
123102 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 ));
103+ .addSpanExporterCustomizer (this ::customizeSpanExporter )
104+ .addMetricExporterCustomizer (this ::customizeMetricExporter )
105+ .addResourceCustomizer (this ::customizeResource );
133106 }
134107
135108 @ Override
136109 public int order () {
137110 return Integer .MAX_VALUE - 1 ;
138111 }
139112
140- private static SpanExporter customizeSpanExporter (
141- SpanExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
113+ private synchronized GoogleCredentials getCredentials () {
114+ if (credentials == null ) {
115+ try {
116+ credentials = GoogleCredentials .getApplicationDefault ();
117+ } catch (IOException e ) {
118+ throw new GoogleAuthException (Reason .FAILED_ADC_RETRIEVAL , e );
119+ }
120+ }
121+ return credentials ;
122+ }
123+
124+ private SpanExporter customizeSpanExporter (
125+ SpanExporter exporter , ConfigProperties configProperties ) {
142126 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 );
127+ return addAuthorizationHeaders (exporter , configProperties );
150128 }
151129 return exporter ;
152130 }
153131
154- private static MetricExporter customizeMetricExporter (
155- MetricExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
132+ private MetricExporter customizeMetricExporter (
133+ MetricExporter exporter , ConfigProperties configProperties ) {
156134 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 );
135+ return addAuthorizationHeaders (exporter , configProperties );
164136 }
165137 return exporter ;
166138 }
@@ -171,11 +143,25 @@ private static boolean isSignalTargeted(String checkSignal, ConfigProperties con
171143 if (endpoint == null ) {
172144 endpoint = configProperties .getString ("otel.exporter.otlp.endpoint" );
173145 }
174- if (endpoint == null
175- || (!endpoint .startsWith ("https://telemetry.googleapis.com" )
176- && !endpoint .startsWith ("https://telemetry.mtls.googleapis.com" ))) {
146+ if (endpoint == null ) {
177147 return false ;
178148 }
149+
150+ try {
151+ java .net .URI uri = new java .net .URI (endpoint );
152+ String host = uri .getHost ();
153+ String scheme = uri .getScheme ();
154+ if (host == null
155+ || scheme == null
156+ || !scheme .equalsIgnoreCase ("https" )
157+ || (!host .equalsIgnoreCase ("telemetry.googleapis.com" )
158+ && !host .equalsIgnoreCase ("telemetry.mtls.googleapis.com" ))) {
159+ return false ;
160+ }
161+ } catch (java .net .URISyntaxException e ) {
162+ return false ;
163+ }
164+
179165 String userSpecifiedTargetedSignals =
180166 ConfigurableOption .GOOGLE_OTEL_AUTH_TARGET_SIGNALS .getConfiguredValueWithFallback (
181167 configProperties , () -> SIGNAL_TYPE_ALL );
@@ -186,57 +172,74 @@ private static boolean isSignalTargeted(String checkSignal, ConfigProperties con
186172 targetedSignal .equals (checkSignal ) || targetedSignal .equals (SIGNAL_TYPE_ALL ));
187173 }
188174
175+ private boolean isAnySignalTargeted (ConfigProperties configProperties ) {
176+ return isSignalTargeted (SIGNAL_TYPE_TRACES , configProperties )
177+ || isSignalTargeted (SIGNAL_TYPE_METRICS , configProperties );
178+ }
179+
189180 // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and
190181 // OtlpHttpSpanExporter.
191- private static SpanExporter addAuthorizationHeaders (
192- SpanExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
182+ private SpanExporter addAuthorizationHeaders (
183+ SpanExporter exporter , ConfigProperties configProperties ) {
193184 if (exporter instanceof OtlpHttpSpanExporter ) {
194- OtlpHttpSpanExporterBuilder builder =
185+ SpanExporter result =
195186 ((OtlpHttpSpanExporter ) exporter )
196187 .toBuilder ()
197- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
198- return builder .build ();
188+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
189+ .build ();
190+ exporter .shutdown ();
191+ return result ;
199192 } else if (exporter instanceof OtlpGrpcSpanExporter ) {
200- OtlpGrpcSpanExporterBuilder builder =
193+ SpanExporter result =
201194 ((OtlpGrpcSpanExporter ) exporter )
202195 .toBuilder ()
203- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
204- return builder .build ();
196+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
197+ .build ();
198+ exporter .shutdown ();
199+ return result ;
205200 }
206201 return exporter ;
207202 }
208203
209204 // Adds authorization headers to the calls made by the OtlpGrpcMetricExporter and
210205 // OtlpHttpMetricExporter.
211- private static MetricExporter addAuthorizationHeaders (
212- MetricExporter exporter , GoogleCredentials credentials , ConfigProperties configProperties ) {
206+ private MetricExporter addAuthorizationHeaders (
207+ MetricExporter exporter , ConfigProperties configProperties ) {
213208 if (exporter instanceof OtlpHttpMetricExporter ) {
214- OtlpHttpMetricExporterBuilder builder =
209+ MetricExporter result =
215210 ((OtlpHttpMetricExporter ) exporter )
216211 .toBuilder ()
217- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
218- return builder .build ();
212+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
213+ .build ();
214+ exporter .shutdown ();
215+ return result ;
219216 } else if (exporter instanceof OtlpGrpcMetricExporter ) {
220- OtlpGrpcMetricExporterBuilder builder =
217+ MetricExporter result =
221218 ((OtlpGrpcMetricExporter ) exporter )
222219 .toBuilder ()
223- .setHeaders (() -> getRequiredHeaderMap (credentials , configProperties ));
224- return builder .build ();
220+ .setHeaders (() -> getRequiredHeaderMap (configProperties ))
221+ .build ();
222+ exporter .shutdown ();
223+ return result ;
225224 }
226225 return exporter ;
227226 }
228227
229- private static Map <String , String > getRequiredHeaderMap (
230- GoogleCredentials credentials , ConfigProperties configProperties ) {
228+ private Map <String , String > getRequiredHeaderMap (ConfigProperties configProperties ) {
229+ GoogleCredentials creds = getCredentials ();
231230 Map <String , List <String >> gcpHeaders ;
232231 try {
233232 // this also refreshes the credentials, if required
234- gcpHeaders = credentials .getRequestMetadata ();
233+ gcpHeaders = creds .getRequestMetadata ();
235234 } catch (IOException e ) {
236235 throw new GoogleAuthException (Reason .FAILED_ADC_REFRESH , e );
237236 }
237+ if (gcpHeaders == null ) {
238+ return Map .of ();
239+ }
238240 Map <String , String > flattenedHeaders =
239241 gcpHeaders .entrySet ().stream ()
242+ .filter (entry -> entry .getKey () != null && entry .getValue () != null )
240243 .collect (
241244 toMap (
242245 Map .Entry ::getKey ,
@@ -259,13 +262,16 @@ private static Map<String, String> getRequiredHeaderMap(
259262 }
260263
261264 // 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 ) {
265+ private Resource customizeResource (Resource resource , ConfigProperties configProperties ) {
266+ if (!isAnySignalTargeted (configProperties )) {
267+ return resource ;
268+ }
269+
264270 String gcpProjectId ;
265271 try {
266272 gcpProjectId = ConfigurableOption .GOOGLE_CLOUD_PROJECT .getConfiguredValue (configProperties );
267273 } catch (ConfigurationException e ) {
268- gcpProjectId = credentials .getProjectId ();
274+ gcpProjectId = getCredentials () .getProjectId ();
269275 if (gcpProjectId == null || gcpProjectId .isEmpty ()) {
270276 throw e ;
271277 }
0 commit comments