diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index f81e5e521..21b816f0c 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { compileOnly("com.google.auto.service:auto-service-annotations") compileOnly("io.opentelemetry:opentelemetry-api") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator") + compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin @@ -36,7 +38,9 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") testImplementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations") + testImplementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") testImplementation("org.awaitility:awaitility") testImplementation("org.mockito:mockito-inline") @@ -130,3 +134,11 @@ tasks.register("IntegrationTestUserCreds") { "-Dmockserver.logLevel=trace" ) } + +// todo remove when https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/14497 is merged +configurations.all { + resolutionStrategy { + force("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.20.0-alpha-SNAPSHOT") + force("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.19.0") + } +} diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java index 639207909..572baa8b8 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java @@ -8,8 +8,8 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import java.util.Locale; -import java.util.Optional; -import java.util.function.Supplier; +import java.util.function.BiFunction; +import javax.annotation.Nullable; /** * An enum representing configurable options for a GCP Authentication Extension. Each option has a @@ -92,57 +92,41 @@ String getUserReadableName() { } /** - * Retrieves the configured value for this option. This method checks the environment variable - * first and then the system property. + * Retrieves the configured value for this option. * * @return The configured value as a string, or throws an exception if not configured. * @throws ConfigurationException if neither the environment variable nor the system property is * set. */ - String getConfiguredValue(ConfigProperties configProperties) { - String configuredValue = configProperties.getString(this.getSystemProperty()); - if (configuredValue != null && !configuredValue.isEmpty()) { - return configuredValue; - } else { + T getRequiredConfiguredValue( + ConfigProperties configProperties, BiFunction extractor) { + T configuredValue = getConfiguredValue(configProperties, extractor); + if (configuredValue == null) { throw new ConfigurationException( String.format( - "GCP Authentication Extension not configured properly: %s not configured. Configure it by exporting environment variable %s or system property %s", + "GCP Authentication Extension not configured properly: %s not configured. " + + "Configure it by exporting environment variable %s or system property %s", this.userReadableName, this.getEnvironmentVariable(), this.getSystemProperty())); } + return configuredValue; } /** - * Retrieves the value for this option, prioritizing environment variables and system properties. - * If neither an environment variable nor a system property is set for this option, the provided - * fallback function is used to determine the value. + * Retrieves the configured value for this option. * - * @param fallback A {@link Supplier} that provides the default value for the option when it is - * not explicitly configured via an environment variable or system property. - * @return The configured value for the option, obtained from the environment variable, system - * property, or the fallback function, in that order of precedence. + * @return The configured value as a string, or {@code null} if not configured. */ - String getConfiguredValueWithFallback( - ConfigProperties configProperties, Supplier fallback) { - try { - return this.getConfiguredValue(configProperties); - } catch (ConfigurationException e) { - return fallback.get(); + @Nullable + T getConfiguredValue( + ConfigProperties configProperties, BiFunction extractor) { + T configuredValue = extractor.apply(configProperties, this.getSystemProperty()); + if (configuredValue instanceof String) { + String value = (String) configuredValue; + if (value.isEmpty()) { + configuredValue = null; // Treat empty string as not configured + } } - } - /** - * Retrieves the value for this option, prioritizing environment variables before system - * properties. If neither an environment variable nor a system property is set for this option, - * then an empty {@link Optional} is returned. - * - * @return The configured value for the option, if set, obtained from the environment variable, - * system property, or empty {@link Optional}, in that order of precedence. - */ - Optional getConfiguredValueAsOptional(ConfigProperties configProperties) { - try { - return Optional.of(this.getConfiguredValue(configProperties)); - } catch (ConfigurationException e) { - return Optional.empty(); - } + return configuredValue; } } diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 1de583029..5b3cc925d 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -7,6 +7,7 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.auto.service.AutoService; +import com.google.common.annotations.VisibleForTesting; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason; @@ -25,7 +26,7 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -84,8 +85,8 @@ public class GcpAuthAutoConfigurationCustomizerProvider * customizes only the signal specific exporter. * * - * The 'customization' performed includes customizing the exporters by adding required headers to - * the export calls made and customizing the resource by adding required resource attributes to + *

The 'customization' performed includes customizing the exporters by adding required headers + * to the export calls made and customizing the resource by adding required resource attributes to * enable GCP integration. * * @param autoConfiguration the AutoConfigurationCustomizer to customize. @@ -96,12 +97,7 @@ public class GcpAuthAutoConfigurationCustomizerProvider */ @Override public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { - GoogleCredentials credentials; - try { - credentials = GoogleCredentials.getApplicationDefault(); - } catch (IOException e) { - throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); - } + GoogleCredentials credentials = getCredentials(); autoConfiguration .addSpanExporterCustomizer( (spanExporter, configProperties) -> @@ -112,6 +108,16 @@ public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource); } + static GoogleCredentials getCredentials() { + GoogleCredentials credentials; + try { + credentials = GoogleCredentials.getApplicationDefault(); + } catch (IOException e) { + throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); + } + return credentials; + } + @Override public int order() { return Integer.MAX_VALUE - 1; @@ -119,44 +125,63 @@ public int order() { private static SpanExporter customizeSpanExporter( SpanExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) { - if (isSignalTargeted(SIGNAL_TYPE_TRACES, configProperties)) { + if (shouldCustomizeExporter( + SIGNAL_TYPE_TRACES, SIGNAL_TARGET_WARNING_FIX_SUGGESTION, configProperties)) { return addAuthorizationHeaders(exporter, credentials, configProperties); - } else { - String[] params = {SIGNAL_TYPE_TRACES, SIGNAL_TARGET_WARNING_FIX_SUGGESTION}; - logger.log( - Level.WARNING, - "GCP Authentication Extension is not configured for signal type: {0}. {1}", - params); } return exporter; } private static MetricExporter customizeMetricExporter( MetricExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) { - if (isSignalTargeted(SIGNAL_TYPE_METRICS, configProperties)) { + if (shouldCustomizeExporter( + SIGNAL_TYPE_METRICS, SIGNAL_TARGET_WARNING_FIX_SUGGESTION, configProperties)) { return addAuthorizationHeaders(exporter, credentials, configProperties); + } + return exporter; + } + + /** + * Utility method to check whether OTLP exporters should be customized for the given target + * signal. + * + * @param signal The target signal to check against. Could be one of {@value SIGNAL_TYPE_TRACES}, + * {@value SIGNAL_TYPE_METRICS} or {@value SIGNAL_TYPE_ALL}. + * @param fixSuggestion A warning to alert the user that auth extension is not configured for the + * provided target signal. + * @param configProperties The {@link ConfigProperties} object used to configure the extension. + * @return A boolean indicating whether the OTLP exporters should be customized or not. + */ + static boolean shouldCustomizeExporter( + String signal, String fixSuggestion, ConfigProperties configProperties) { + if (isSignalTargeted(signal, configProperties)) { + return true; } else { - String[] params = {SIGNAL_TYPE_METRICS, SIGNAL_TARGET_WARNING_FIX_SUGGESTION}; logger.log( Level.WARNING, "GCP Authentication Extension is not configured for signal type: {0}. {1}", - params); + new String[] {signal, fixSuggestion}); + return false; } - return exporter; } // Checks if the auth extension is configured to target the passed signal for authentication. private static boolean isSignalTargeted(String checkSignal, ConfigProperties configProperties) { - String userSpecifiedTargetedSignals = - ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValueWithFallback( - configProperties, () -> SIGNAL_TYPE_ALL); - return Arrays.stream(userSpecifiedTargetedSignals.split(",")) - .map(String::trim) + return targetSignals(configProperties).stream() .anyMatch( targetedSignal -> targetedSignal.equals(checkSignal) || targetedSignal.equals(SIGNAL_TYPE_ALL)); } + @VisibleForTesting + static List targetSignals(ConfigProperties configProperties) { + return Objects.requireNonNull( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValue( + configProperties, + (properties, name) -> + properties.getList(name, Collections.singletonList(SIGNAL_TYPE_ALL)))); + } + // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and // OtlpHttpSpanExporter. private static SpanExporter addAuthorizationHeaders( @@ -193,7 +218,7 @@ private static MetricExporter addAuthorizationHeaders( return exporter; } - private static Map getRequiredHeaderMap( + static Map getRequiredHeaderMap( GoogleCredentials credentials, ConfigProperties configProperties) { Map> gcpHeaders; try { @@ -216,23 +241,31 @@ private static Map getRequiredHeaderMap( // Add quota user project header if not detected by the auth library and user provided it via // system properties. if (!flattenedHeaders.containsKey(QUOTA_USER_PROJECT_HEADER)) { - Optional maybeConfiguredQuotaProjectId = - ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValueAsOptional( - configProperties); - maybeConfiguredQuotaProjectId.ifPresent( - configuredQuotaProjectId -> - flattenedHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId)); + getQuotaProjectId(configProperties) + .ifPresent( + configuredQuotaProjectId -> + flattenedHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId)); } return flattenedHeaders; } + static Optional getQuotaProjectId(ConfigProperties configProperties) { + return Optional.ofNullable( + ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValue( + configProperties, ConfigProperties::getString)); + } + // Updates the current resource with the attributes required for ingesting OTLP data on GCP. private static Resource customizeResource(Resource resource, ConfigProperties configProperties) { - String gcpProjectId = - ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValue(configProperties); Resource res = Resource.create( - Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId)); + Attributes.of( + AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), getProjectId(configProperties))); return resource.merge(res); } + + static String getProjectId(ConfigProperties configProperties) { + return ConfigurableOption.GOOGLE_CLOUD_PROJECT.getRequiredConfiguredValue( + configProperties, ConfigProperties::getString); + } } diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthDeclarativeConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthDeclarativeConfigurationCustomizerProvider.java new file mode 100644 index 000000000..8ac60431f --- /dev/null +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthDeclarativeConfigurationCustomizerProvider.java @@ -0,0 +1,172 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.auth; + +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_METRICS; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_TRACES; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.shouldCustomizeExporter; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auto.service.AutoService; +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.instrumentation.api.incubator.config.bridge.ConfigPropertiesUtil; +import io.opentelemetry.instrumentation.api.incubator.config.bridge.DeclarativeConfigPropertiesBridgeBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizer; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.SdkConfigProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProviderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReaderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.NameStringValuePairModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpGrpcExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpGrpcMetricExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpHttpExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpHttpMetricExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleSpanProcessorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProviderModel; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +@AutoService(DeclarativeConfigurationCustomizerProvider.class) +public class GcpAuthDeclarativeConfigurationCustomizerProvider + implements DeclarativeConfigurationCustomizerProvider { + + static final String SIGNAL_TARGET_WARNING_YAML_FIX_SUGGESTION = + String.format( + "You may safely ignore this warning if it is intentional, otherwise please configure the '%s' by setting %s in the configuration file.", + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getUserReadableName(), + ConfigPropertiesUtil.propertyYamlPath( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty())); + + @Override + public void customize(DeclarativeConfigurationCustomizer customizer) { + customizer.addModelCustomizer( + model -> { + customize( + model, + GcpAuthAutoConfigurationCustomizerProvider.getCredentials(), + getConfigProperties(model)); + + return model; + }); + } + + @VisibleForTesting + static void customize( + OpenTelemetryConfigurationModel model, + GoogleCredentials credentials, + ConfigProperties configProperties) { + Map headerMap = + GcpAuthAutoConfigurationCustomizerProvider.getRequiredHeaderMap( + credentials, configProperties); + customizeMeter(model, headerMap, configProperties); + customizeTracer(model, headerMap, configProperties); + } + + static ConfigProperties getConfigProperties(OpenTelemetryConfigurationModel model) { + return new DeclarativeConfigPropertiesBridgeBuilder() + .buildFromInstrumentationConfig(SdkConfigProvider.create(model).getInstrumentationConfig()); + } + + private static void customizeMeter( + OpenTelemetryConfigurationModel model, + Map headerMap, + ConfigProperties configProperties) { + MeterProviderModel meterProvider = model.getMeterProvider(); + if (meterProvider == null) { + return; + } + + if (shouldCustomizeExporter( + SIGNAL_TYPE_METRICS, SIGNAL_TARGET_WARNING_YAML_FIX_SUGGESTION, configProperties)) { + for (MetricReaderModel reader : meterProvider.getReaders()) { + if (reader.getPeriodic() != null) { + addAuth(meterModelHeaders(reader.getPeriodic().getExporter()), headerMap); + } + } + } + } + + private static List> meterModelHeaders( + @Nullable PushMetricExporterModel exporter) { + ArrayList> list = new ArrayList<>(); + if (exporter == null) { + return list; + } + OtlpGrpcMetricExporterModel grpc = exporter.getOtlpGrpc(); + if (grpc != null) { + list.add(grpc.getHeaders()); + } + OtlpHttpMetricExporterModel http = exporter.getOtlpHttp(); + if (http != null) { + list.add(http.getHeaders()); + } + return list; + } + + private static void customizeTracer( + OpenTelemetryConfigurationModel model, + Map headerMap, + ConfigProperties configProperties) { + TracerProviderModel tracerProvider = model.getTracerProvider(); + if (tracerProvider == null) { + return; + } + + if (shouldCustomizeExporter( + SIGNAL_TYPE_TRACES, SIGNAL_TARGET_WARNING_YAML_FIX_SUGGESTION, configProperties)) { + for (SpanProcessorModel processor : tracerProvider.getProcessors()) { + BatchSpanProcessorModel batch = processor.getBatch(); + if (batch != null) { + addAuth(spanExporterModelHeaders(batch.getExporter()), headerMap); + } + SimpleSpanProcessorModel simple = processor.getSimple(); + if (simple != null) { + addAuth(spanExporterModelHeaders(simple.getExporter()), headerMap); + } + } + } + } + + private static List> spanExporterModelHeaders( + @Nullable SpanExporterModel exporter) { + ArrayList> list = new ArrayList<>(); + + if (exporter == null) { + return list; + } + OtlpGrpcExporterModel grpc = exporter.getOtlpGrpc(); + if (grpc != null) { + list.add(grpc.getHeaders()); + } + OtlpHttpExporterModel http = exporter.getOtlpHttp(); + if (http != null) { + list.add(http.getHeaders()); + } + return list; + } + + private static void addAuth( + List> headerConsumers, Map headerMap) { + headerConsumers.forEach(headers -> addHeaders(headers, headerMap)); + } + + private static void addHeaders(List headers, Map add) { + add.forEach( + (key, value) -> { + if (headers.stream().noneMatch(header -> key.equals(header.getName()))) { + headers.add(new NameStringValuePairModel().withName(key).withValue(value)); + } + }); + } +} diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthDeclarativeConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthDeclarativeConfigurationCustomizerProviderTest.java new file mode 100644 index 000000000..ca234a0e3 --- /dev/null +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthDeclarativeConfigurationCustomizerProviderTest.java @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.auth; + +import static io.opentelemetry.contrib.gcp.auth.GcpAuthDeclarativeConfigurationCustomizerProvider.SIGNAL_TARGET_WARNING_YAML_FIX_SUGGESTION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.auth.oauth2.GoogleCredentials; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class GcpAuthDeclarativeConfigurationCustomizerProviderTest { + + @Test + void declarativeConfig() throws IOException { + String yaml = + "file_format: 0.4\n" + + "tracer_provider:\n" + + " processors:\n" + + " - simple:\n" + + " exporter:\n" + + " otlp_http:\n" + + "meter_provider:\n" + + " readers:\n" + + " - periodic:\n" + + " exporter:\n" + + " otlp_http:\n" + + "instrumentation/development:\n" + + " java:\n" + + " google:\n" + + " cloud:\n" + + " project: p\n" + + " quota:\n" + + " project: qp\n" + + " otel:\n" + + " auth:\n" + + " target:\n" + + " signals: [metrics, traces]\n"; + + OpenTelemetryConfigurationModel model = + DeclarativeConfiguration.parse( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); + ConfigProperties properties = + GcpAuthDeclarativeConfigurationCustomizerProvider.getConfigProperties(model); + + assertThat(GcpAuthAutoConfigurationCustomizerProvider.targetSignals(properties)) + .containsExactly("metrics", "traces"); + assertThat(GcpAuthAutoConfigurationCustomizerProvider.getProjectId(properties)).isEqualTo("p"); + assertThat(GcpAuthAutoConfigurationCustomizerProvider.getQuotaProjectId(properties)) + .contains("qp"); + + GoogleCredentials credentials = mock(GoogleCredentials.class); + when(credentials.getRequestMetadata()) + .thenReturn( + Collections.singletonMap("x-goog-user-project", Collections.singletonList("qp"))); + + GcpAuthDeclarativeConfigurationCustomizerProvider.customize(model, credentials, properties); + + String header = + "headers=\\[io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.NameStringValuePairModel@.*\\[name=x-goog-user-project,value=qp]"; + // both metrics and traces should have the header + assertThat(model.toString()).matches(String.format(".*%s.*%s.*", header, header)); + } + + @Test + void fixSuggestion() { + assertThat(SIGNAL_TARGET_WARNING_YAML_FIX_SUGGESTION) + .isEqualTo( + "You may safely ignore this warning if it is intentional, " + + "otherwise please configure the 'Target Signals for Google Authentication Extension' " + + "by setting " + + "'instrumentation/development' / 'java' / 'google' / 'otel' / 'auth' / 'target' / " + + "'signals' in the configuration file."); + } +} diff --git a/span-stacktrace/build.gradle.kts b/span-stacktrace/build.gradle.kts index 4033b0177..2a32b8b50 100644 --- a/span-stacktrace/build.gradle.kts +++ b/span-stacktrace/build.gradle.kts @@ -15,8 +15,13 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator") + compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + testImplementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") compileOnly("io.opentelemetry.semconv:opentelemetry-semconv") testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") @@ -26,3 +31,11 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-exporter-logging") } + +// todo remove when https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/14497 is merged +configurations.all { + resolutionStrategy { + force("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.20.0-alpha-SNAPSHOT") + force("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.19.0") + } +} diff --git a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfig.java b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfig.java index 2315d2a10..189934689 100644 --- a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfig.java +++ b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfig.java @@ -23,25 +23,26 @@ public class StackTraceAutoConfig implements AutoConfigurationCustomizerProvider private static final Logger log = Logger.getLogger(StackTraceAutoConfig.class.getName()); - private static final String CONFIG_MIN_DURATION = - "otel.java.experimental.span-stacktrace.min.duration"; + static final String PREFIX = "otel.java.experimental.span-stacktrace."; + private static final String CONFIG_MIN_DURATION = PREFIX + "min.duration"; private static final Duration CONFIG_MIN_DURATION_DEFAULT = Duration.ofMillis(5); - - private static final String CONFIG_FILTER = "otel.java.experimental.span-stacktrace.filter"; + private static final String CONFIG_FILTER = PREFIX + "filter"; @Override public void customize(AutoConfigurationCustomizer config) { config.addTracerProviderCustomizer( (providerBuilder, properties) -> { - long minDuration = getMinDuration(properties); - if (minDuration >= 0) { - Predicate filter = getFilterPredicate(properties); - providerBuilder.addSpanProcessor(new StackTraceSpanProcessor(minDuration, filter)); + if (getMinDuration(properties) >= 0) { + providerBuilder.addSpanProcessor(create(properties)); } return providerBuilder; }); } + static StackTraceSpanProcessor create(ConfigProperties properties) { + return new StackTraceSpanProcessor(getMinDuration(properties), getFilterPredicate(properties)); + } + // package-private for testing static long getMinDuration(ConfigProperties properties) { long minDuration = diff --git a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProvider.java b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProvider.java new file mode 100644 index 000000000..a71275ead --- /dev/null +++ b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.stacktrace; + +import com.google.auto.service.AutoService; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.bridge.DeclarativeConfigPropertiesBridgeBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.trace.SpanProcessor; + +@SuppressWarnings("rawtypes") +@AutoService(ComponentProvider.class) +public class StackTraceComponentProvider implements ComponentProvider { + @Override + public String getName() { + return "experimental-stacktrace"; + } + + @Override + public SpanProcessor create(DeclarativeConfigProperties config) { + return StackTraceAutoConfig.create( + new DeclarativeConfigPropertiesBridgeBuilder() + .addMapping(StackTraceAutoConfig.PREFIX, "") + .build(config)); + } + + @Override + public Class getType() { + return SpanProcessor.class; + } +} diff --git a/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProviderTest.java b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProviderTest.java new file mode 100644 index 000000000..a2b4c4212 --- /dev/null +++ b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProviderTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.stacktrace; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class StackTraceComponentProviderTest { + @Test + void endToEnd() { + String yaml = + "file_format: 0.4\n" + + "tracer_provider:\n" + + " processors:\n" + + " - experimental-stacktrace:\n"; + + OpenTelemetrySdk openTelemetrySdk = + DeclarativeConfiguration.parseAndCreate( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); + + assertThat(openTelemetrySdk.getSdkTracerProvider().toString()) + .contains("StackTraceSpanProcessor"); + } +}